/* eslint-disable unicorn/prefer-set-has, unicorn/no-array-sort, regexp/no-unused-capturing-group, unicorn/no-nested-ternary, unicorn/no-array-reduce */
// @ts-nocheck Mock 扩展接口数据结构较大，关闭严格类型检查。
import Mock from 'mockjs';

/**
 * 文件职责：商品管理扩展模块 Mock 接口。
 * 1. 提供分类管理、规格做法、加料管理、商品标签、时段供应接口。
 * 2. 提供商品选择器与批量工具扩展接口。
 */
interface MockRequestOptions {
  body: null | string;
  type: string;
  url: string;
}

type ProductCategoryChannel = 'dine_in' | 'pickup' | 'wm';
type ProductStatus = 'off_shelf' | 'on_sale' | 'sold_out';
type ProductSwitchStatus = 'disabled' | 'enabled';

interface ProductPickerRecord {
  categoryId: string;
  id: string;
  name: string;
  price: number;
  spuCode: string;
  status: ProductStatus;
}

interface CategoryRecord {
  channels: ProductCategoryChannel[];
  description: string;
  icon: string;
  id: string;
  name: string;
  sort: number;
  status: ProductSwitchStatus;
}

interface SpecValueRecord {
  extraPrice: number;
  id: string;
  name: string;
  sort: number;
}

interface SpecRecord {
  description: string;
  id: string;
  name: string;
  productIds: string[];
  sort: number;
  status: ProductSwitchStatus;
  updatedAt: string;
  values: SpecValueRecord[];
}

interface AddonItemRecord {
  id: string;
  name: string;
  price: number;
  stock: number;
  sort: number;
  status: ProductSwitchStatus;
}

interface AddonGroupRecord {
  description: string;
  id: string;
  items: AddonItemRecord[];
  maxSelect: number;
  minSelect: number;
  name: string;
  productIds: string[];
  required: boolean;
  sort: number;
  status: ProductSwitchStatus;
  updatedAt: string;
}

interface LabelRecord {
  color: string;
  id: string;
  name: string;
  productCount: number;
  sort: number;
  status: ProductSwitchStatus;
  updatedAt: string;
}

interface ScheduleRecord {
  endTime: string;
  id: string;
  name: string;
  productIds: string[];
  startTime: string;
  status: ProductSwitchStatus;
  updatedAt: string;
  weekDays: number[];
}

interface ProductExtensionStoreState {
  addonGroups: AddonGroupRecord[];
  categories: CategoryRecord[];
  labels: LabelRecord[];
  products: ProductPickerRecord[];
  schedules: ScheduleRecord[];
  specs: SpecRecord[];
}

interface ProductBatchScope {
  categoryId?: string;
  productIds?: string[];
  type: 'all' | 'category' | 'selected';
}

const CATEGORY_SEEDS = [
  { id: 'cat-hot', name: '热销推荐', sort: 1, description: '高销量推荐菜品' },
  { id: 'cat-main', name: '主食', sort: 2, description: '主食与招牌正餐' },
  { id: 'cat-snack', name: '小吃凉菜', sort: 3, description: '小食与凉拌菜' },
  { id: 'cat-soup', name: '汤羹粥品', sort: 4, description: '汤羹与粥品' },
  { id: 'cat-drink', name: '饮品', sort: 5, description: '冷热饮品与甜品' },
  { id: 'cat-combo', name: '套餐', sort: 6, description: '多人份组合套餐' },
];

const PRODUCT_SEEDS = [
  { categoryId: 'cat-main', name: '宫保鸡丁', price: 32 },
  { categoryId: 'cat-main', name: '鱼香肉丝', price: 28 },
  { categoryId: 'cat-main', name: '红烧排骨', price: 42 },
  { categoryId: 'cat-main', name: '照烧鸡腿饭', price: 27 },
  { categoryId: 'cat-snack', name: '蒜香鸡翅', price: 24 },
  { categoryId: 'cat-snack', name: '凉拌黄瓜', price: 10 },
  { categoryId: 'cat-snack', name: '椒盐玉米', price: 14 },
  { categoryId: 'cat-soup', name: '酸辣汤', price: 18 },
  { categoryId: 'cat-soup', name: '番茄蛋花汤', price: 16 },
  { categoryId: 'cat-drink', name: '杨枝甘露', price: 16 },
  { categoryId: 'cat-drink', name: '冰粉', price: 8 },
  { categoryId: 'cat-drink', name: '柠檬气泡水', price: 9 },
  { categoryId: 'cat-combo', name: '双人午市套餐', price: 69 },
  { categoryId: 'cat-combo', name: '单人畅享套餐', price: 39 },
  { categoryId: 'cat-hot', name: '招牌牛肉饭', price: 34 },
  { categoryId: 'cat-hot', name: '经典鸡排饭', price: 26 },
];

const storeMap = new Map<string, ProductExtensionStoreState>();
let idSeed = 10_000;
const ENABLE_PRODUCT_CATEGORY_MOCK =
  import.meta.env.VITE_MOCK_PRODUCT_CATEGORY === 'true';

function parseUrlParams(url: string) {
  const parsed = new URL(url, 'http://localhost');
  const params: Record<string, string> = {};
  parsed.searchParams.forEach((value, key) => {
    params[key] = value;
  });
  return params;
}

function parseBody(options: MockRequestOptions) {
  if (!options.body) return {};
  try {
    return JSON.parse(options.body) as Record<string, unknown>;
  } catch (error) {
    console.error('[mock-product-extensions] parseBody error:', error);
    return {};
  }
}

function toDateTimeText(date: Date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hour = String(date.getHours()).padStart(2, '0');
  const minute = String(date.getMinutes()).padStart(2, '0');
  const second = String(date.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}

function createId(prefix: string, storeId: string) {
  idSeed += 1;
  return `${prefix}-${storeId}-${idSeed}`;
}

function normalizeText(value: unknown, fallback = '') {
  const text = String(value ?? '').trim();
  return text || fallback;
}

function normalizeNumber(value: unknown, fallback = 0, min = 0) {
  const parsed = Number(value);
  if (!Number.isFinite(parsed)) return fallback;
  return Math.max(min, parsed);
}

function normalizeInt(value: unknown, fallback = 0, min = 0) {
  return Math.floor(normalizeNumber(value, fallback, min));
}

function normalizeBool(value: unknown, fallback = false) {
  if (typeof value === 'boolean') return value;
  if (typeof value === 'number') return value === 1;
  if (typeof value === 'string') {
    const next = value.trim().toLowerCase();
    if (next === 'true' || next === '1') return true;
    if (next === 'false' || next === '0') return false;
  }
  return fallback;
}

function normalizeSwitchStatus(
  value: unknown,
  fallback: ProductSwitchStatus,
): ProductSwitchStatus {
  const next = String(value || '').trim();
  return next === 'enabled' || next === 'disabled' ? next : fallback;
}

function normalizeProductStatus(
  value: unknown,
  fallback: ProductStatus,
): ProductStatus {
  const next = String(value || '').trim();
  return next === 'off_shelf' || next === 'on_sale' || next === 'sold_out'
    ? next
    : fallback;
}

function normalizeIdList(value: unknown) {
  if (!Array.isArray(value)) return [];
  return [
    ...new Set(value.map((item) => String(item || '').trim()).filter(Boolean)),
  ];
}

function normalizeChannels(value: unknown, fallback: ProductCategoryChannel[]) {
  if (!Array.isArray(value)) return [...fallback];
  const allowed: ProductCategoryChannel[] = ['wm', 'pickup', 'dine_in'];
  const list = value
    .map((item) => String(item || '').trim())
    .filter((item): item is ProductCategoryChannel =>
      allowed.includes(item as ProductCategoryChannel),
    );
  const unique = [...new Set(list)];
  return unique.length > 0 ? unique : [...fallback];
}

function normalizeWeekDays(value: unknown) {
  if (!Array.isArray(value)) return [1, 2, 3, 4, 5, 6, 7];
  const list = value
    .map((item) => normalizeInt(item, 0, 0))
    .filter((item) => item >= 1 && item <= 7);
  const unique = [...new Set(list)].sort((a, b) => a - b);
  return unique.length > 0 ? unique : [1, 2, 3, 4, 5, 6, 7];
}

function normalizeTimeText(value: unknown, fallback: string) {
  const text = normalizeText(value, fallback);
  if (/^([01]\d|2[0-3]):[0-5]\d$/.test(text)) return text;
  return fallback;
}

function getCategoryName(
  state: ProductExtensionStoreState,
  categoryId: string,
) {
  return (
    state.categories.find((item) => item.id === categoryId)?.name ??
    state.categories[0]?.name ??
    '未分类'
  );
}

function hasCategory(state: ProductExtensionStoreState, categoryId: string) {
  return state.categories.some((item) => item.id === categoryId);
}

function cleanupRelationIds(state: ProductExtensionStoreState) {
  const idSet = new Set(state.products.map((item) => item.id));
  for (const spec of state.specs) {
    spec.productIds = spec.productIds.filter((item) => idSet.has(item));
  }
  for (const group of state.addonGroups) {
    group.productIds = group.productIds.filter((item) => idSet.has(item));
  }
  for (const schedule of state.schedules) {
    schedule.productIds = schedule.productIds.filter((item) => idSet.has(item));
  }
}

function resolveScopedProducts(
  state: ProductExtensionStoreState,
  scope: ProductBatchScope,
) {
  const type = String(scope?.type || 'all');
  if (type === 'selected') {
    const idSet = new Set(normalizeIdList(scope?.productIds));
    return state.products.filter((item) => idSet.has(item.id));
  }
  if (type === 'category') {
    const categoryId = normalizeText(scope?.categoryId);
    if (!categoryId) return [];
    return state.products.filter((item) => item.categoryId === categoryId);
  }
  return [...state.products];
}

function toCategoryManageItem(
  state: ProductExtensionStoreState,
  item: CategoryRecord,
) {
  return {
    id: item.id,
    name: item.name,
    sort: item.sort,
    description: item.description,
    icon: item.icon,
    channels: [...item.channels],
    status: item.status,
    productCount: state.products.filter(
      (product) => product.categoryId === item.id,
    ).length,
  };
}

function toSpecItem(state: ProductExtensionStoreState, item: SpecRecord) {
  const idSet = new Set(state.products.map((product) => product.id));
  const productIds = item.productIds.filter((id) => idSet.has(id));
  return {
    id: item.id,
    name: item.name,
    description: item.description,
    sort: item.sort,
    status: item.status,
    productIds,
    productCount: productIds.length,
    updatedAt: item.updatedAt,
    values: item.values
      .toSorted((a, b) => a.sort - b.sort)
      .map((value) => ({
        id: value.id,
        name: value.name,
        sort: value.sort,
        extraPrice: value.extraPrice,
      })),
  };
}

function toAddonGroupItem(
  state: ProductExtensionStoreState,
  item: AddonGroupRecord,
) {
  const idSet = new Set(state.products.map((product) => product.id));
  const productIds = item.productIds.filter((id) => idSet.has(id));
  return {
    id: item.id,
    name: item.name,
    description: item.description,
    required: item.required,
    minSelect: item.minSelect,
    maxSelect: item.maxSelect,
    sort: item.sort,
    status: item.status,
    productIds,
    productCount: productIds.length,
    updatedAt: item.updatedAt,
    items: item.items
      .toSorted((a, b) => a.sort - b.sort)
      .map((addon) => ({
        id: addon.id,
        name: addon.name,
        price: addon.price,
        stock: addon.stock,
        sort: addon.sort,
        status: addon.status,
      })),
  };
}

function toLabelItem(item: LabelRecord) {
  return {
    id: item.id,
    name: item.name,
    color: item.color,
    sort: item.sort,
    status: item.status,
    productCount: item.productCount,
    updatedAt: item.updatedAt,
  };
}

function toScheduleItem(
  state: ProductExtensionStoreState,
  item: ScheduleRecord,
) {
  const idSet = new Set(state.products.map((product) => product.id));
  const productIds = item.productIds.filter((id) => idSet.has(id));
  return {
    id: item.id,
    name: item.name,
    startTime: item.startTime,
    endTime: item.endTime,
    weekDays: [...item.weekDays],
    status: item.status,
    productIds,
    productCount: productIds.length,
    updatedAt: item.updatedAt,
  };
}

function createDefaultState(storeId: string): ProductExtensionStoreState {
  const categories: CategoryRecord[] = CATEGORY_SEEDS.map((item) => ({
    id: item.id,
    name: item.name,
    sort: item.sort,
    description: item.description,
    icon: 'lucide:folder',
    channels: ['wm', 'pickup', 'dine_in'],
    status: 'enabled',
  }));

  const products: ProductPickerRecord[] = PRODUCT_SEEDS.map((item, index) => ({
    id: `ext-prd-${storeId}-${String(index + 1).padStart(4, '0')}`,
    categoryId: item.categoryId,
    name: item.name,
    price: item.price,
    spuCode: `SPU2026${String(10_000 + index).slice(-5)}`,
    status:
      index % 7 === 0 ? 'sold_out' : index % 5 === 0 ? 'off_shelf' : 'on_sale',
  }));

  const specs: SpecRecord[] = [
    {
      id: createId('spec', storeId),
      name: '份量',
      description: '控制半份/整份售卖',
      sort: 1,
      status: 'enabled',
      productIds: products.slice(0, 8).map((item) => item.id),
      updatedAt: toDateTimeText(new Date()),
      values: [
        {
          id: createId('specv', storeId),
          name: '半份',
          sort: 1,
          extraPrice: -3,
        },
        {
          id: createId('specv', storeId),
          name: '整份',
          sort: 2,
          extraPrice: 0,
        },
      ],
    },
    {
      id: createId('spec', storeId),
      name: '辣度',
      description: '顾客按口味选择辣度',
      sort: 2,
      status: 'enabled',
      productIds: products.slice(0, 10).map((item) => item.id),
      updatedAt: toDateTimeText(new Date()),
      values: [
        {
          id: createId('specv', storeId),
          name: '不辣',
          sort: 1,
          extraPrice: 0,
        },
        {
          id: createId('specv', storeId),
          name: '微辣',
          sort: 2,
          extraPrice: 0,
        },
        {
          id: createId('specv', storeId),
          name: '中辣',
          sort: 3,
          extraPrice: 0,
        },
      ],
    },
  ];

  const addonGroups: AddonGroupRecord[] = [
    {
      id: createId('addon', storeId),
      name: '主食加量',
      description: '适用于主食类商品',
      required: false,
      minSelect: 0,
      maxSelect: 2,
      sort: 1,
      status: 'enabled',
      productIds: products.slice(0, 10).map((item) => item.id),
      updatedAt: toDateTimeText(new Date()),
      items: [
        {
          id: createId('addon-item', storeId),
          name: '加鸡蛋',
          price: 2,
          stock: 160,
          sort: 1,
          status: 'enabled',
        },
        {
          id: createId('addon-item', storeId),
          name: '加饭',
          price: 3,
          stock: 80,
          sort: 2,
          status: 'enabled',
        },
      ],
    },
  ];

  const labels: LabelRecord[] = [
    {
      id: createId('label', storeId),
      name: '招牌',
      color: '#cf1322',
      sort: 1,
      status: 'enabled',
      productCount: 6,
      updatedAt: toDateTimeText(new Date()),
    },
    {
      id: createId('label', storeId),
      name: '新品',
      color: '#2f54eb',
      sort: 2,
      status: 'enabled',
      productCount: 6,
      updatedAt: toDateTimeText(new Date()),
    },
  ];

  const schedules: ScheduleRecord[] = [
    {
      id: createId('schedule', storeId),
      name: '午市供应',
      startTime: '10:30',
      endTime: '14:00',
      weekDays: [1, 2, 3, 4, 5, 6, 7],
      status: 'enabled',
      productIds: products.slice(0, 8).map((item) => item.id),
      updatedAt: toDateTimeText(new Date()),
    },
  ];

  return {
    categories,
    products,
    specs,
    addonGroups,
    labels,
    schedules,
  };
}

function ensureStoreState(storeId = '') {
  const key = storeId || 'default';
  let state = storeMap.get(key);
  if (!state) {
    state = createDefaultState(key);
    storeMap.set(key, state);
  }
  return state;
}

// 分类管理与商品选择器默认走真实 TenantApi，需手动开启才启用 mock。
if (ENABLE_PRODUCT_CATEGORY_MOCK) {
  Mock.mock(
    /\/product\/category\/manage\/list(?:\?|$)/,
    'get',
    (options: MockRequestOptions) => {
      const params = parseUrlParams(options.url);
      const storeId = normalizeText(params.storeId);
      const keyword = normalizeText(params.keyword).toLowerCase();
      const status = normalizeText(params.status);
      const state = ensureStoreState(storeId);

      const list = state.categories
        .toSorted((a, b) => a.sort - b.sort)
        .filter((item) => {
          if (status && item.status !== status) return false;
          if (!keyword) return true;
          return (
            item.name.toLowerCase().includes(keyword) ||
            item.description.toLowerCase().includes(keyword)
          );
        })
        .map((item) => toCategoryManageItem(state, item));

      return { code: 200, data: list };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/save/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const id = normalizeText(body.id);
      const name = normalizeText(body.name);
      if (!storeId || !name) {
        return { code: 400, data: null, message: '参数不完整' };
      }

      const state = ensureStoreState(storeId);
      const duplicate = state.categories.find(
        (item) => item.name === name && item.id !== id,
      );
      if (duplicate) {
        return { code: 400, data: null, message: '分类名称已存在' };
      }

      const currentSortMax =
        state.categories.reduce((max, item) => Math.max(max, item.sort), 0) + 1;
      const existingIndex = state.categories.findIndex(
        (item) => item.id === id,
      );

      const next =
        existingIndex === -1
          ? {
              id: createId('cat', storeId),
              name,
              description: normalizeText(body.description),
              icon: normalizeText(body.icon, 'lucide:folder'),
              sort: normalizeInt(body.sort, currentSortMax, 1),
              status: normalizeSwitchStatus(body.status, 'enabled'),
              channels: normalizeChannels(body.channels, ['wm']),
            }
          : {
              ...state.categories[existingIndex],
              name,
              description: normalizeText(
                body.description,
                state.categories[existingIndex].description,
              ),
              icon: normalizeText(
                body.icon,
                state.categories[existingIndex].icon,
              ),
              sort: normalizeInt(
                body.sort,
                state.categories[existingIndex].sort,
                1,
              ),
              status: normalizeSwitchStatus(
                body.status,
                state.categories[existingIndex].status,
              ),
              channels: normalizeChannels(
                body.channels,
                state.categories[existingIndex].channels,
              ),
            };

      if (existingIndex === -1) {
        state.categories.push(next);
      } else {
        state.categories.splice(existingIndex, 1, next);
      }

      return { code: 200, data: toCategoryManageItem(state, next) };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/delete/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const categoryId = normalizeText(body.categoryId);
      if (!storeId || !categoryId) {
        return { code: 400, data: null, message: '参数不完整' };
      }

      const state = ensureStoreState(storeId);
      const hasProducts = state.products.some(
        (item) => item.categoryId === categoryId,
      );
      if (hasProducts) {
        return { code: 400, data: null, message: '分类下仍有商品，不能删除' };
      }

      if (state.categories.length <= 1) {
        return { code: 400, data: null, message: '至少保留一个分类' };
      }

      state.categories = state.categories.filter(
        (item) => item.id !== categoryId,
      );
      return { code: 200, data: null };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/status/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const categoryId = normalizeText(body.categoryId);
      if (!storeId || !categoryId) {
        return { code: 400, data: null, message: '参数不完整' };
      }
      const state = ensureStoreState(storeId);
      const target = state.categories.find((item) => item.id === categoryId);
      if (!target) return { code: 404, data: null, message: '分类不存在' };
      target.status = normalizeSwitchStatus(body.status, target.status);
      return { code: 200, data: toCategoryManageItem(state, target) };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/sort/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const items = Array.isArray(body.items) ? body.items : [];
      if (!storeId) return { code: 400, data: null, message: '参数不完整' };

      const sortMap = new Map<string, number>();
      for (const item of items) {
        if (!item || typeof item !== 'object') continue;
        const current = item as Record<string, unknown>;
        const id = normalizeText(current.categoryId);
        const sort = normalizeInt(current.sort, 0, 1);
        if (!id || sort <= 0) continue;
        sortMap.set(id, sort);
      }

      const state = ensureStoreState(storeId);
      state.categories = state.categories
        .map((item) => ({
          ...item,
          sort: sortMap.get(item.id) ?? item.sort,
        }))
        .toSorted((a, b) => a.sort - b.sort)
        .map((item, index) => ({ ...item, sort: index + 1 }));

      return {
        code: 200,
        data: state.categories.map((item) => toCategoryManageItem(state, item)),
      };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/products\/bind/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const categoryId = normalizeText(body.categoryId);
      const productIds = normalizeIdList(body.productIds);
      if (!storeId || !categoryId || productIds.length === 0) {
        return { code: 400, data: null, message: '参数不完整' };
      }

      const state = ensureStoreState(storeId);
      if (!hasCategory(state, categoryId)) {
        return { code: 404, data: null, message: '分类不存在' };
      }

      const idSet = new Set(productIds);
      let successCount = 0;
      for (const item of state.products) {
        if (!idSet.has(item.id)) continue;
        item.categoryId = categoryId;
        successCount += 1;
      }

      return {
        code: 200,
        data: {
          totalCount: productIds.length,
          successCount,
          failedCount: Math.max(productIds.length - successCount, 0),
        },
      };
    },
  );

  Mock.mock(
    /\/product\/category\/manage\/products\/unbind/,
    'post',
    (options: MockRequestOptions) => {
      const body = parseBody(options);
      const storeId = normalizeText(body.storeId);
      const categoryId = normalizeText(body.categoryId);
      const productId = normalizeText(body.productId);
      if (!storeId || !categoryId || !productId) {
        return { code: 400, data: null, message: '参数不完整' };
      }

      const state = ensureStoreState(storeId);
      const fallbackCategory = state.categories.find(
        (item) => item.id !== categoryId,
      );
      if (!fallbackCategory) {
        return { code: 400, data: null, message: '无可用目标分类' };
      }

      const target = state.products.find((item) => item.id === productId);
      if (!target) return { code: 404, data: null, message: '商品不存在' };
      target.categoryId = fallbackCategory.id;

      return { code: 200, data: target };
    },
  );

  Mock.mock(
    /\/product\/picker\/list(?:\?|$)/,
    'get',
    (options: MockRequestOptions) => {
      const params = parseUrlParams(options.url);
      const storeId = normalizeText(params.storeId);
      const keyword = normalizeText(params.keyword).toLowerCase();
      const categoryId = normalizeText(params.categoryId);
      const limit = Math.max(
        1,
        Math.min(500, normalizeInt(params.limit, 200, 1)),
      );
      const state = ensureStoreState(storeId);

      const list = state.products
        .filter((item) => {
          if (categoryId && item.categoryId !== categoryId) return false;
          if (!keyword) return true;
          return (
            item.name.toLowerCase().includes(keyword) ||
            item.spuCode.toLowerCase().includes(keyword)
          );
        })
        .slice(0, limit)
        .map((item) => ({
          id: item.id,
          name: item.name,
          spuCode: item.spuCode,
          categoryId: item.categoryId,
          categoryName: getCategoryName(state, item.categoryId),
          price: item.price,
          status: item.status,
        }));

      return { code: 200, data: list };
    },
  );
}

Mock.mock(
  /\/product\/spec\/list(?:\?|$)/,
  'get',
  (options: MockRequestOptions) => {
    const params = parseUrlParams(options.url);
    const storeId = normalizeText(params.storeId);
    const keyword = normalizeText(params.keyword).toLowerCase();
    const status = normalizeText(params.status);
    const state = ensureStoreState(storeId);

    const list = state.specs
      .toSorted((a, b) => a.sort - b.sort)
      .filter((item) => {
        if (status && item.status !== status) return false;
        if (!keyword) return true;
        return (
          item.name.toLowerCase().includes(keyword) ||
          item.description.toLowerCase().includes(keyword)
        );
      })
      .map((item) => toSpecItem(state, item));

    return { code: 200, data: list };
  },
);

Mock.mock(/\/product\/spec\/save/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const id = normalizeText(body.id);
  const name = normalizeText(body.name);
  if (!storeId || !name) {
    return { code: 400, data: null, message: '参数不完整' };
  }

  const state = ensureStoreState(storeId);
  const existingIndex = state.specs.findIndex((item) => item.id === id);
  const fallbackSort =
    state.specs.reduce((max, item) => Math.max(max, item.sort), 0) + 1;
  const productIds = normalizeIdList(body.productIds).filter((productId) =>
    state.products.some((item) => item.id === productId),
  );

  const valueListRaw = Array.isArray(body.values) ? body.values : [];
  const values: SpecValueRecord[] = valueListRaw
    .map((value, index) => {
      if (!value || typeof value !== 'object') return null;
      const current = value as Record<string, unknown>;
      const valueName = normalizeText(current.name);
      if (!valueName) return null;
      return {
        id: normalizeText(current.id, createId('specv', storeId)),
        name: valueName,
        sort: normalizeInt(current.sort, index + 1, 1),
        extraPrice: Number(
          normalizeNumber(current.extraPrice, 0, -999).toFixed(2),
        ),
      };
    })
    .filter((item): item is SpecValueRecord => item !== null)
    .toSorted((a, b) => a.sort - b.sort);

  if (values.length === 0) {
    return { code: 400, data: null, message: '至少保留一个规格值' };
  }

  const next: SpecRecord =
    existingIndex === -1
      ? {
          id: createId('spec', storeId),
          name,
          description: normalizeText(body.description),
          sort: normalizeInt(body.sort, fallbackSort, 1),
          status: normalizeSwitchStatus(body.status, 'enabled'),
          productIds,
          values,
          updatedAt: toDateTimeText(new Date()),
        }
      : {
          ...state.specs[existingIndex],
          name,
          description: normalizeText(
            body.description,
            state.specs[existingIndex].description,
          ),
          sort: normalizeInt(body.sort, state.specs[existingIndex].sort, 1),
          status: normalizeSwitchStatus(
            body.status,
            state.specs[existingIndex].status,
          ),
          productIds,
          values,
          updatedAt: toDateTimeText(new Date()),
        };

  if (existingIndex === -1) {
    state.specs.push(next);
  } else {
    state.specs.splice(existingIndex, 1, next);
  }

  return { code: 200, data: toSpecItem(state, next) };
});

Mock.mock(/\/product\/spec\/delete/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const specId = normalizeText(body.specId);
  if (!storeId || !specId)
    return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  state.specs = state.specs.filter((item) => item.id !== specId);
  return { code: 200, data: null };
});

Mock.mock(/\/product\/spec\/status/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const specId = normalizeText(body.specId);
  if (!storeId || !specId)
    return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  const target = state.specs.find((item) => item.id === specId);
  if (!target) return { code: 404, data: null, message: '规格不存在' };
  target.status = normalizeSwitchStatus(body.status, target.status);
  target.updatedAt = toDateTimeText(new Date());
  return { code: 200, data: toSpecItem(state, target) };
});

Mock.mock(
  /\/product\/addon\/group\/list(?:\?|$)/,
  'get',
  (options: MockRequestOptions) => {
    const params = parseUrlParams(options.url);
    const storeId = normalizeText(params.storeId);
    const keyword = normalizeText(params.keyword).toLowerCase();
    const status = normalizeText(params.status);
    const state = ensureStoreState(storeId);

    const list = state.addonGroups
      .toSorted((a, b) => a.sort - b.sort)
      .filter((item) => {
        if (status && item.status !== status) return false;
        if (!keyword) return true;
        return (
          item.name.toLowerCase().includes(keyword) ||
          item.description.toLowerCase().includes(keyword)
        );
      })
      .map((item) => toAddonGroupItem(state, item));

    return { code: 200, data: list };
  },
);

Mock.mock(
  /\/product\/addon\/group\/save/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const id = normalizeText(body.id);
    const name = normalizeText(body.name);
    if (!storeId || !name) {
      return { code: 400, data: null, message: '参数不完整' };
    }

    const state = ensureStoreState(storeId);
    const existingIndex = state.addonGroups.findIndex((item) => item.id === id);
    const fallbackSort =
      state.addonGroups.reduce((max, item) => Math.max(max, item.sort), 0) + 1;
    const productIds = normalizeIdList(body.productIds).filter((productId) =>
      state.products.some((item) => item.id === productId),
    );

    const itemListRaw = Array.isArray(body.items) ? body.items : [];
    const items: AddonItemRecord[] = itemListRaw
      .map((item, index) => {
        if (!item || typeof item !== 'object') return null;
        const current = item as Record<string, unknown>;
        const itemName = normalizeText(current.name);
        if (!itemName) return null;
        return {
          id: normalizeText(current.id, createId('addon-item', storeId)),
          name: itemName,
          price: Number(normalizeNumber(current.price, 0, 0).toFixed(2)),
          stock: normalizeInt(current.stock, 999, 0),
          sort: normalizeInt(current.sort, index + 1, 1),
          status: normalizeSwitchStatus(current.status, 'enabled'),
        };
      })
      .filter((item): item is AddonItemRecord => item !== null)
      .toSorted((a, b) => a.sort - b.sort);

    if (items.length === 0) {
      return { code: 400, data: null, message: '至少保留一个加料项' };
    }

    const next: AddonGroupRecord =
      existingIndex === -1
        ? {
            id: createId('addon', storeId),
            name,
            description: normalizeText(body.description),
            required: normalizeBool(body.required, false),
            minSelect: normalizeInt(body.minSelect, 0, 0),
            maxSelect: normalizeInt(body.maxSelect, 1, 1),
            sort: normalizeInt(body.sort, fallbackSort, 1),
            status: normalizeSwitchStatus(body.status, 'enabled'),
            productIds,
            items,
            updatedAt: toDateTimeText(new Date()),
          }
        : {
            ...state.addonGroups[existingIndex],
            name,
            description: normalizeText(
              body.description,
              state.addonGroups[existingIndex].description,
            ),
            required: normalizeBool(
              body.required,
              state.addonGroups[existingIndex].required,
            ),
            minSelect: normalizeInt(
              body.minSelect,
              state.addonGroups[existingIndex].minSelect,
              0,
            ),
            maxSelect: normalizeInt(
              body.maxSelect,
              state.addonGroups[existingIndex].maxSelect,
              1,
            ),
            sort: normalizeInt(
              body.sort,
              state.addonGroups[existingIndex].sort,
              1,
            ),
            status: normalizeSwitchStatus(
              body.status,
              state.addonGroups[existingIndex].status,
            ),
            productIds,
            items,
            updatedAt: toDateTimeText(new Date()),
          };

    if (next.maxSelect < next.minSelect) {
      next.maxSelect = next.minSelect;
    }

    if (existingIndex === -1) {
      state.addonGroups.push(next);
    } else {
      state.addonGroups.splice(existingIndex, 1, next);
    }

    return { code: 200, data: toAddonGroupItem(state, next) };
  },
);

Mock.mock(
  /\/product\/addon\/group\/delete/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const groupId = normalizeText(body.groupId);
    if (!storeId || !groupId) {
      return { code: 400, data: null, message: '参数不完整' };
    }
    const state = ensureStoreState(storeId);
    state.addonGroups = state.addonGroups.filter((item) => item.id !== groupId);
    return { code: 200, data: null };
  },
);

Mock.mock(
  /\/product\/addon\/group\/status/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const groupId = normalizeText(body.groupId);
    if (!storeId || !groupId) {
      return { code: 400, data: null, message: '参数不完整' };
    }
    const state = ensureStoreState(storeId);
    const target = state.addonGroups.find((item) => item.id === groupId);
    if (!target) return { code: 404, data: null, message: '加料组不存在' };
    target.status = normalizeSwitchStatus(body.status, target.status);
    target.updatedAt = toDateTimeText(new Date());
    return { code: 200, data: toAddonGroupItem(state, target) };
  },
);

Mock.mock(
  /\/product\/addon\/group\/products\/bind/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const groupId = normalizeText(body.groupId);
    if (!storeId || !groupId) {
      return { code: 400, data: null, message: '参数不完整' };
    }

    const state = ensureStoreState(storeId);
    const target = state.addonGroups.find((item) => item.id === groupId);
    if (!target) return { code: 404, data: null, message: '加料组不存在' };

    target.productIds = normalizeIdList(body.productIds).filter((productId) =>
      state.products.some((item) => item.id === productId),
    );
    target.updatedAt = toDateTimeText(new Date());
    return { code: 200, data: toAddonGroupItem(state, target) };
  },
);

Mock.mock(
  /\/product\/label\/list(?:\?|$)/,
  'get',
  (options: MockRequestOptions) => {
    const params = parseUrlParams(options.url);
    const storeId = normalizeText(params.storeId);
    const keyword = normalizeText(params.keyword).toLowerCase();
    const status = normalizeText(params.status);
    const state = ensureStoreState(storeId);

    const list = state.labels
      .toSorted((a, b) => a.sort - b.sort)
      .filter((item) => {
        if (status && item.status !== status) return false;
        if (!keyword) return true;
        return item.name.toLowerCase().includes(keyword);
      })
      .map((item) => toLabelItem(item));

    return { code: 200, data: list };
  },
);

Mock.mock(/\/product\/label\/save/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const id = normalizeText(body.id);
  const name = normalizeText(body.name);
  if (!storeId || !name) {
    return { code: 400, data: null, message: '参数不完整' };
  }

  const state = ensureStoreState(storeId);
  const existingIndex = state.labels.findIndex((item) => item.id === id);
  const fallbackSort =
    state.labels.reduce((max, item) => Math.max(max, item.sort), 0) + 1;

  const next: LabelRecord =
    existingIndex === -1
      ? {
          id: createId('label', storeId),
          name,
          color: normalizeText(body.color, '#1677ff'),
          sort: normalizeInt(body.sort, fallbackSort, 1),
          status: normalizeSwitchStatus(body.status, 'enabled'),
          productCount: 0,
          updatedAt: toDateTimeText(new Date()),
        }
      : {
          ...state.labels[existingIndex],
          name,
          color: normalizeText(body.color, state.labels[existingIndex].color),
          sort: normalizeInt(body.sort, state.labels[existingIndex].sort, 1),
          status: normalizeSwitchStatus(
            body.status,
            state.labels[existingIndex].status,
          ),
          updatedAt: toDateTimeText(new Date()),
        };

  if (existingIndex === -1) {
    state.labels.push(next);
  } else {
    state.labels.splice(existingIndex, 1, next);
  }

  return { code: 200, data: toLabelItem(next) };
});

Mock.mock(/\/product\/label\/delete/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const labelId = normalizeText(body.labelId);
  if (!storeId || !labelId)
    return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  state.labels = state.labels.filter((item) => item.id !== labelId);
  return { code: 200, data: null };
});

Mock.mock(/\/product\/label\/status/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  const labelId = normalizeText(body.labelId);
  if (!storeId || !labelId)
    return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  const target = state.labels.find((item) => item.id === labelId);
  if (!target) return { code: 404, data: null, message: '标签不存在' };
  target.status = normalizeSwitchStatus(body.status, target.status);
  target.updatedAt = toDateTimeText(new Date());
  return { code: 200, data: toLabelItem(target) };
});

Mock.mock(
  /\/product\/schedule\/list(?:\?|$)/,
  'get',
  (options: MockRequestOptions) => {
    const params = parseUrlParams(options.url);
    const storeId = normalizeText(params.storeId);
    const keyword = normalizeText(params.keyword).toLowerCase();
    const status = normalizeText(params.status);
    const state = ensureStoreState(storeId);

    const list = state.schedules
      .toSorted((a, b) => a.name.localeCompare(b.name))
      .filter((item) => {
        if (status && item.status !== status) return false;
        if (!keyword) return true;
        return item.name.toLowerCase().includes(keyword);
      })
      .map((item) => toScheduleItem(state, item));

    return { code: 200, data: list };
  },
);

Mock.mock(
  /\/product\/schedule\/save/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const id = normalizeText(body.id);
    const name = normalizeText(body.name);
    if (!storeId || !name) {
      return { code: 400, data: null, message: '参数不完整' };
    }

    const state = ensureStoreState(storeId);
    const existingIndex = state.schedules.findIndex((item) => item.id === id);
    const productIds = normalizeIdList(body.productIds).filter((productId) =>
      state.products.some((item) => item.id === productId),
    );
    const startTime = normalizeTimeText(body.startTime, '09:00');
    const endTime = normalizeTimeText(body.endTime, '21:00');
    const weekDays = normalizeWeekDays(body.weekDays);
    if (productIds.length === 0) {
      return { code: 400, data: null, message: '请至少关联一个商品' };
    }

    const next: ScheduleRecord =
      existingIndex === -1
        ? {
            id: createId('schedule', storeId),
            name,
            startTime,
            endTime,
            weekDays,
            status: normalizeSwitchStatus(body.status, 'enabled'),
            productIds,
            updatedAt: toDateTimeText(new Date()),
          }
        : {
            ...state.schedules[existingIndex],
            name,
            startTime,
            endTime,
            weekDays,
            status: normalizeSwitchStatus(
              body.status,
              state.schedules[existingIndex].status,
            ),
            productIds,
            updatedAt: toDateTimeText(new Date()),
          };

    if (existingIndex === -1) {
      state.schedules.push(next);
    } else {
      state.schedules.splice(existingIndex, 1, next);
    }

    return { code: 200, data: toScheduleItem(state, next) };
  },
);

Mock.mock(
  /\/product\/schedule\/delete/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const scheduleId = normalizeText(body.scheduleId);
    if (!storeId || !scheduleId) {
      return { code: 400, data: null, message: '参数不完整' };
    }
    const state = ensureStoreState(storeId);
    state.schedules = state.schedules.filter((item) => item.id !== scheduleId);
    return { code: 200, data: null };
  },
);

Mock.mock(
  /\/product\/schedule\/status/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const scheduleId = normalizeText(body.scheduleId);
    if (!storeId || !scheduleId) {
      return { code: 400, data: null, message: '参数不完整' };
    }
    const state = ensureStoreState(storeId);
    const target = state.schedules.find((item) => item.id === scheduleId);
    if (!target) return { code: 404, data: null, message: '时段不存在' };
    target.status = normalizeSwitchStatus(body.status, target.status);
    target.updatedAt = toDateTimeText(new Date());
    return { code: 200, data: toScheduleItem(state, target) };
  },
);

Mock.mock(
  /\/product\/batch\/price-adjust/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const mode = normalizeText(body.mode, 'increase');
    const amount = normalizeNumber(body.amount, 0, 0);
    if (!storeId || amount < 0) {
      return { code: 400, data: null, message: '参数不完整' };
    }

    const scope = (body.scope ?? { type: 'all' }) as ProductBatchScope;
    const state = ensureStoreState(storeId);
    const targets = resolveScopedProducts(state, scope);

    for (const item of targets) {
      if (mode === 'set') {
        item.price = Number(amount.toFixed(2));
      } else if (mode === 'decrease') {
        item.price = Number(Math.max(0, item.price - amount).toFixed(2));
      } else {
        item.price = Number((item.price + amount).toFixed(2));
      }
    }

    return {
      code: 200,
      data: {
        totalCount: targets.length,
        successCount: targets.length,
        failedCount: 0,
      },
    };
  },
);

Mock.mock(
  /\/product\/batch\/move-category/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const targetCategoryId = normalizeText(body.targetCategoryId);
    if (!storeId || !targetCategoryId) {
      return { code: 400, data: null, message: '参数不完整' };
    }
    const state = ensureStoreState(storeId);
    if (!hasCategory(state, targetCategoryId)) {
      return { code: 404, data: null, message: '目标分类不存在' };
    }

    const scope = (body.scope ?? { type: 'all' }) as ProductBatchScope;
    const targets = resolveScopedProducts(state, scope);
    for (const item of targets) {
      item.categoryId = targetCategoryId;
    }

    return {
      code: 200,
      data: {
        totalCount: targets.length,
        successCount: targets.length,
        failedCount: 0,
      },
    };
  },
);

Mock.mock(
  /\/product\/batch\/sale-switch/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const storeId = normalizeText(body.storeId);
    const action = normalizeText(body.action, 'off');
    if (!storeId) return { code: 400, data: null, message: '参数不完整' };

    const scope = (body.scope ?? { type: 'all' }) as ProductBatchScope;
    const state = ensureStoreState(storeId);
    const targets = resolveScopedProducts(state, scope);
    for (const item of targets) {
      item.status = normalizeProductStatus(
        action === 'on' ? 'on_sale' : 'off_shelf',
        item.status,
      );
    }

    return {
      code: 200,
      data: {
        totalCount: targets.length,
        successCount: targets.length,
        failedCount: 0,
      },
    };
  },
);

Mock.mock(
  /\/product\/batch\/store-sync/,
  'post',
  (options: MockRequestOptions) => {
    const body = parseBody(options);
    const sourceStoreId = normalizeText(body.sourceStoreId || body.storeId);
    const targetStoreIds = normalizeIdList(body.targetStoreIds);
    const productIds = normalizeIdList(body.productIds);
    if (
      !sourceStoreId ||
      targetStoreIds.length === 0 ||
      productIds.length === 0
    ) {
      return { code: 400, data: null, message: '参数不完整' };
    }

    const source = ensureStoreState(sourceStoreId);
    const sourceProducts = source.products.filter((item) =>
      productIds.includes(item.id),
    );
    if (sourceProducts.length === 0) {
      return { code: 400, data: null, message: '未找到待同步商品' };
    }

    let successCount = 0;

    for (const targetStoreId of targetStoreIds) {
      const target = ensureStoreState(targetStoreId);
      for (const sourceProduct of sourceProducts) {
        const targetCategoryId = hasCategory(target, sourceProduct.categoryId)
          ? sourceProduct.categoryId
          : target.categories[0]?.id;
        if (!targetCategoryId) continue;

        const sameSpu = target.products.find(
          (item) => item.spuCode === sourceProduct.spuCode,
        );
        if (sameSpu) {
          sameSpu.name = sourceProduct.name;
          sameSpu.price = sourceProduct.price;
          sameSpu.categoryId = targetCategoryId;
          sameSpu.status = sourceProduct.status;
        } else {
          target.products.push({
            ...sourceProduct,
            id: createId('ext-prd', targetStoreId),
            categoryId: targetCategoryId,
          });
        }
        successCount += 1;
      }
      cleanupRelationIds(target);
    }

    return {
      code: 200,
      data: {
        totalCount: sourceProducts.length * targetStoreIds.length,
        successCount,
        failedCount: Math.max(
          sourceProducts.length * targetStoreIds.length - successCount,
          0,
        ),
      },
    };
  },
);

Mock.mock(/\/product\/batch\/import/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  if (!storeId) return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  const scope = (body.scope ?? { type: 'all' }) as ProductBatchScope;
  const targets = resolveScopedProducts(state, scope);
  return {
    code: 200,
    data: {
      fileName: `product-import-${storeId}.xlsx`,
      totalCount: targets.length,
      successCount: targets.length,
      failedCount: 0,
      skippedCount: 0,
    },
  };
});

Mock.mock(/\/product\/batch\/export/, 'post', (options: MockRequestOptions) => {
  const body = parseBody(options);
  const storeId = normalizeText(body.storeId);
  if (!storeId) return { code: 400, data: null, message: '参数不完整' };
  const state = ensureStoreState(storeId);
  const scope = (body.scope ?? { type: 'all' }) as ProductBatchScope;
  const targets = resolveScopedProducts(state, scope);
  return {
    code: 200,
    data: {
      fileName: `product-export-${storeId}.xlsx`,
      totalCount: targets.length,
      successCount: targets.length,
      failedCount: 0,
      exportedCount: targets.length,
    },
  };
});
