import { useCallback, useEffect, useMemo, useState } from "react";
import { parseForm } from "@/shared/forms/validation";
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import { emptyUserIdentityFilter, userIdentityFilterParams } from "@/shared/ui/userIdentityFilter.js";
import {
    batchCreateGifts,
    batchDeleteResources as requestBatchDeleteResources,
    createEmojiPack,
    createGift,
    createResource,
    createResourceGroup,
    deleteGift as requestDeleteGift,
    deleteResource as requestDeleteResource,
    disableResourceShopItem,
    disableGift,
    disableResource,
    disableResourceGroup,
    enableResourceShopItem,
    enableGift,
    enableResource,
    enableResourceGroup,
    getResourceIdentityAutoGrantConfig,
    grantResource,
    grantResourceGroup,
    listEmojiPackCategories,
    listEmojiPacks,
    listGiftTypes,
    listGifts,
    listResourceGrants,
    listResourceGroups,
    listResourceShopItems,
    listResourceShopPurchaseOrders,
    listResources,
    lookupResourceGrantTarget,
    revokeResourceGrant,
    updateGift,
    updateGiftTypes,
    updateResource,
    updateResourceIdentityAutoGrantConfig,
    updateResourceGroup,
    upsertResourceShopItems,
} from "@/features/resources/api";
import { getVipConfig, getVipProgram, grantVip, grantVipTrialCard } from "@/features/vip-config/api";
import {
    cpRelationTypeLabels,
    cpRelationTypeOptions,
    defaultGiftTypeOptions,
    resourceIdentityAutoGrantOptions,
    resourceShopDurationOptions,
    resourceShopSellableTypes,
} from "@/features/resources/constants.js";
import { useResourceAbilities } from "@/features/resources/permissions.js";
import {
    resourceMetadataJSON,
    resourceRequiresExplicitVisualFormat,
    roomBorderFormatFromMetadata,
    roomBorderFormatMetadataJSON,
    roomNameStyleFormFromMetadata,
    roomNameStyleMetadataJSON,
} from "@/features/resources/resourceMetadata.js";
import {
    emojiPackFormSchema,
    giftFormSchema,
    giftTypeConfigFormSchema,
    resourceFormSchema,
    resourceGrantFormSchema,
    resourceGroupCreateFormSchema,
    resourceIdentityAutoGrantFormSchema,
    resourceShopItemsFormSchema,
} from "@/features/resources/schema.js";

const pageSize = 50;
const optionPageSize = 100;
const optionMaxPages = 200;
const dayMillis = 24 * 60 * 60 * 1000;
const cpGiftTypeCode = "cp";
const defaultCPRelationType = cpRelationTypeOptions[0][0];
const cpRelationTypeValues = new Set(cpRelationTypeOptions.map(([value]) => value));
const cpPresentationRelationKeys = ["cp_relation_type", "cpRelationType"];
const cpPresentationLabelKeys = ["cp_relation_label", "cpRelationLabel"];
const emptyData = { items: [], page: 1, pageSize, total: 0 };
const emptyResourceForm = (resource = {}) => ({
    animationUrl: resource.animationUrl || resource.assetUrl || "",
    avatarFrameKind:
        resource.resourceType === "avatar_frame"
            ? resource.avatarFrameKind || avatarFrameKindFromMetadata(resource.metadataJson) || "normal"
            : "normal",
    badgeForm:
        resource.resourceType === "badge"
            ? resource.badgeForm || badgeFormFromMetadata(resource.metadataJson) || "tile"
            : "tile",
    badgeKind:
        resource.resourceType === "badge"
            ? resource.badgeKind || badgeKindFromMetadata(resource.metadataJson) || "normal"
            : "normal",
    coinPrice: resource.coinPrice === 0 || resource.coinPrice ? String(resource.coinPrice) : "",
    enabled: resource.status ? resource.status === "active" : true,
    levelTrack:
        resource.resourceType === "badge" || resource.resourceType === "avatar_frame"
            ? resource.levelTrack || levelTrackFromMetadata(resource.metadataJson) || ""
            : "",
    managerGrantEnabled:
        resource.managerGrantEnabled === undefined || resource.managerGrantEnabled === null
            ? true
            : Boolean(resource.managerGrantEnabled),
    metadataJson: resource.metadataJson || "",
    name: resource.name || "",
    previewUrl: resource.previewUrl || "",
    priceType: resource.resourceId ? resource.priceType || (resource.coinPrice > 0 ? "coin" : "free") : "coin",
    resourceCode: resource.resourceCode || "",
    resourceType: resource.resourceType || "avatar_frame",
    roomBorderFormat: roomBorderFormatFromMetadata(resource.metadataJson),
    ...roomNameStyleFormFromMetadata(resource.metadataJson),
    walletAssetAmount: resource.walletAssetAmount ? String(resource.walletAssetAmount) : "",
});
const emptyEmojiPackForm = () => ({
    animationUrl: "",
    category: "默认",
    coverUrl: "",
    name: "",
    pricingType: "free",
    regionIds: ["0"],
    sortOrder: "0",
});
const emptyResourceGroupForm = (group = {}) => ({
    description: group.description || "",
    enabled: group.status ? group.status === "active" : true,
    groupCode: group.groupCode || "",
    items: group.items?.length ? group.items.map(groupItemToForm) : [emptyGroupResourceItem()],
    name: group.name || "",
    sortOrder: group.sortOrder === 0 || group.sortOrder ? String(group.sortOrder) : "0",
});
const emptyResourceIdentityAutoGrantForm = (items = []) => {
    const itemByIdentityType = new Map(items.map((item) => [item.identityType, item]));
    return resourceIdentityAutoGrantOptions.map(([identityType]) => ({
        identityType,
        resourceGroupId: itemByIdentityType.get(identityType)?.resourceGroupId
            ? String(itemByIdentityType.get(identityType).resourceGroupId)
            : "",
    }));
};
const emptyGroupResourceItem = () => ({
    durationDays: "1",
    itemType: "resource",
    resourceId: "",
    sortOrder: "0",
    walletAssetAmount: "",
    walletAssetType: "",
});
const emptyGroupWalletAssetItem = (walletAssetType) => ({
    durationDays: "",
    itemType: "wallet_asset",
    resourceId: "",
    sortOrder: "0",
    walletAssetAmount: "",
    walletAssetType,
});
const emptyGiftForm = (gift = {}) => {
    const giftTypeCode = gift.giftTypeCode || "normal";
    const presentationJson = gift.presentationJson || "{}";
    const cpRelationType = cpRelationTypeFromGift({ ...gift, giftTypeCode, presentationJson }) || defaultCPRelationType;
    return {
        chargeAssetType: gift.chargeAssetType || "COIN",
        coinPrice: gift.coinPrice === 0 || gift.coinPrice ? String(gift.coinPrice) : "",
        cpRelationType: giftTypeCode === cpGiftTypeCode ? cpRelationType : "",
        effectTypes: gift.effectTypes || [],
        effectiveFrom: msToDatetimeLocal(gift.effectiveFromMs),
        effectiveTo: msToDatetimeLocal(gift.effectiveToMs),
        enabled: gift.status ? gift.status === "active" : true,
        giftId: gift.giftId || "",
        giftTypeCode,
        name: gift.name || "",
        presentationJson: normalizeGiftPresentationJson(presentationJson, giftTypeCode, cpRelationType),
        priceVersion: gift.priceVersion || "default",
        regionIds: (gift.regionIds?.length ? gift.regionIds : [0]).map(String),
        resourceId: gift.resourceId ? String(gift.resourceId) : "",
        items: [],
        sortOrder: gift.sortOrder === 0 || gift.sortOrder ? String(gift.sortOrder) : "0",
    };
};

export function applyGiftResourceSelection(form, resource, fillIdentity = true) {
    const resourceId = resource?.resourceId ? String(resource.resourceId) : "";
    const nextForm = {
        ...form,
        resourceId,
    };
    if (fillIdentity) {
        nextForm.giftId = resourceId;
        nextForm.name = resource?.name || resource?.resourceCode || resourceId;
    }
    return applyGiftResourcePrice(nextForm, resource);
}

export function buildGiftResourceDrafts(form, resourceIds = [], resources = []) {
    const resourcesById = new Map(resources.map((resource) => [String(resource.resourceId), resource]));
    const existingDraftsByResourceId = new Map(
        (form.items || []).filter((item) => item?.resourceId).map((item) => [String(item.resourceId), item]),
    );

    return resourceIds
        .map((resourceId) => String(resourceId || "").trim())
        .filter(Boolean)
        .map((resourceId) => {
            const existingDraft = existingDraftsByResourceId.get(resourceId);
            if (existingDraft) {
                return existingDraft;
            }
            const resource = resourcesById.get(resourceId) || { resourceId };
            return {
                ...applyGiftResourceSelection(giftDraftDefaultsFromForm(form), resource, true),
                resource,
            };
        });
}

function giftDraftDefaultsFromForm(form) {
    const giftTypeCode = form.giftTypeCode || "normal";
    const cpRelationType = giftTypeCode === cpGiftTypeCode ? form.cpRelationType || defaultCPRelationType : "";
    return {
        chargeAssetType: form.chargeAssetType || "COIN",
        coinPrice: "",
        cpRelationType,
        effectTypes: form.effectTypes || [],
        effectiveFrom: form.effectiveFrom || "",
        effectiveTo: form.effectiveTo || "",
        enabled: form.enabled !== false,
        giftId: "",
        giftTypeCode,
        name: "",
        presentationJson: normalizeGiftPresentationJson(form.presentationJson || "{}", giftTypeCode, cpRelationType),
        priceVersion: form.priceVersion || "default",
        regionIds: form.regionIds?.length ? form.regionIds : ["0"],
        resourceId: "",
        sortOrder: form.sortOrder || "0",
    };
}

export function applyGiftPriceDefaults(form, coinPrice) {
    const priceValue = String(coinPrice ?? "");
    return {
        ...form,
        coinPrice: priceValue,
    };
}

export function cpRelationTypeFromPresentationJson(presentationJson) {
    const presentation = parseGiftPresentationObject(presentationJson);
    const relationType = cpPresentationRelationKeys
        .map((key) =>
            String(presentation?.[key] || "")
                .trim()
                .toLowerCase(),
        )
        .find((value) => cpRelationTypeValues.has(value));
    return relationType || "";
}

export function cpRelationTypeFromGift(gift = {}) {
    if (gift?.giftTypeCode !== cpGiftTypeCode) {
        return "";
    }
    return validCPRelationType(gift.cpRelationType) || cpRelationTypeFromPresentationJson(gift.presentationJson);
}

export function cpRelationTypeShortLabel(value) {
    const label = cpRelationTypeLabels[validCPRelationType(value)] || String(value || "").trim();
    return label.endsWith("礼物") ? label.slice(0, -2) : label;
}

export function normalizeGiftPresentationJson(presentationJson, giftTypeCode, cpRelationType) {
    const rawValue = String(presentationJson || "").trim();
    const isCPGift = giftTypeCode === cpGiftTypeCode;
    let parsedValue;

    // 礼物展示配置由后端透传给 room/wallet 链路；这里只对对象型 JSON 做 CP 字段收敛，避免误改旧的非对象配置。
    try {
        parsedValue = rawValue ? JSON.parse(rawValue) : {};
    } catch {
        return rawValue || "{}";
    }
    if (!parsedValue || typeof parsedValue !== "object" || Array.isArray(parsedValue)) {
        return isCPGift ? cpPresentationJson({}, cpRelationType) : rawValue || "{}";
    }

    const nextPresentation = { ...parsedValue };
    [...cpPresentationRelationKeys, ...cpPresentationLabelKeys].forEach((key) => {
        delete nextPresentation[key];
    });
    return isCPGift ? cpPresentationJson(nextPresentation, cpRelationType) : JSON.stringify(nextPresentation);
}

export async function fetchAllOptionPages(fetcher, query = {}, pageSize = optionPageSize, maxPages = optionMaxPages) {
    const items = [];
    let total = Number.POSITIVE_INFINITY;

    // 后台会把选项接口的 page_size 截断到 100；抽屉候选项必须按真实分页拉全，避免只展示第一页资源。
    for (let page = 1; page <= maxPages; page += 1) {
        const data = await fetcher({ ...query, page, page_size: pageSize });
        const pageItems = Array.isArray(data?.items) ? data.items : [];
        const responsePageSize = Number(data?.pageSize || data?.page_size || pageSize) || pageSize;
        const responseTotal = Number(data?.total);

        items.push(...pageItems);
        if (Number.isFinite(responseTotal) && responseTotal >= 0) {
            total = responseTotal;
        }
        if (!pageItems.length || items.length >= total || pageItems.length < responsePageSize) {
            return {
                ...data,
                items,
                page: 1,
                pageSize: responsePageSize,
                total: Number.isFinite(total) ? total : items.length,
            };
        }
    }

    throw new Error("资源选项分页过多，请收窄筛选条件");
}

export function groupResourceShopItems(items = []) {
    const itemsByResourceId = new Map();

    // 同一资源可以同时售卖 1/3/7 天规格，按资源分组时必须保留完整数组，不能再用单值 Map 覆盖前面的规格。
    items.forEach((item) => {
        const resourceId = Number(item?.resourceId || 0);
        if (!Number.isInteger(resourceId) || resourceId <= 0) {
            return;
        }
        const resourceItems = itemsByResourceId.get(resourceId) || [];
        resourceItems.push(item);
        itemsByResourceId.set(resourceId, resourceItems);
    });
    itemsByResourceId.forEach((resourceItems) => {
        resourceItems.sort((left, right) => Number(left.durationDays || 0) - Number(right.durationDays || 0));
    });
    return itemsByResourceId;
}

export function buildResourceShopDraftGroup(resource, existingItems = [], fallbackSortOrder = 0) {
    const resourceId = String(resource?.resourceId || "");
    const allowedDurations = new Set(resourceShopDurationOptions.map(([durationDays]) => durationDays));
    const specsByDuration = {};

    existingItems.forEach((item) => {
        const durationDays = String(item?.durationDays || "1");
        if (!allowedDurations.has(durationDays)) {
            return;
        }
        specsByDuration[durationDays] = resourceShopSpecDraft(resource, durationDays, item, fallbackSortOrder);
    });

    // 首次选择资源默认创建 1 天规格并继承资源列表价格；其他天数由运营按需勾选且各自维护价格。
    if (Object.keys(specsByDuration).length === 0) {
        specsByDuration["1"] = resourceShopSpecDraft(resource, "1", null, fallbackSortOrder);
    }
    return { resourceId, specsByDuration };
}

export function canUnselectResourceShopDraftSpec(spec) {
    // 已落库规格只能在商店列表通过状态 Switch 启停；从抽屉静默移除不会删除数据库行，因此必须阻止这种误导操作。
    return !spec?.shopItemId;
}

export function canUnselectResourceShopDraftGroup(draftGroup) {
    // 父级取消会一次丢弃全部规格；只要其中一条已落库，就必须保留整组并让运营逐条通过列表 Switch 启停。
    return Object.values(draftGroup?.specsByDuration || {}).every(canUnselectResourceShopDraftSpec);
}

function resourceShopSpecDraft(resource, durationDays, existingItem, fallbackSortOrder) {
    return {
        coinPrice: String(existingItem?.coinPrice ?? resource?.coinPrice ?? ""),
        durationDays,
        effectiveFromMs: existingItem?.effectiveFromMs || "",
        effectiveToMs: existingItem?.effectiveToMs || "",
        resourceId: String(resource?.resourceId || existingItem?.resourceId || ""),
        shopItemId: existingItem?.shopItemId,
        sortOrder: String(existingItem?.sortOrder ?? fallbackSortOrder),
        status: existingItem?.status || "active",
    };
}

function loadResourceGrantOptions() {
    return Promise.all([
        fetchAllOptionPages(listResources, { status: "active" }),
        fetchAllOptionPages(listGifts, { status: "active" }),
        listGiftTypes(),
        fetchAllOptionPages(listResourceGroups, { status: "active" }),
    ]).then(([resources, gifts, giftTypes, groups]) => ({ gifts, giftTypes, groups, resources }));
}

function parseGiftPresentationObject(presentationJson) {
    const rawValue = String(presentationJson || "").trim();
    if (!rawValue) {
        return {};
    }
    try {
        const parsedValue = JSON.parse(rawValue);
        return parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ? parsedValue : {};
    } catch {
        return {};
    }
}

function normalizedCPRelationType(value) {
    return validCPRelationType(value) || defaultCPRelationType;
}

function validCPRelationType(value) {
    const relationType = String(value || "")
        .trim()
        .toLowerCase();
    return cpRelationTypeValues.has(relationType) ? relationType : "";
}

function cpPresentationJson(basePresentation, cpRelationType) {
    const relationType = normalizedCPRelationType(cpRelationType);
    return JSON.stringify({
        ...basePresentation,
        cp_relation_label: cpRelationTypeLabels[relationType] || relationType,
        cp_relation_type: relationType,
    });
}

function applyGiftResourcePrice(form, resource) {
    const priceType = resource?.priceType || (resource?.coinPrice > 0 ? "coin" : "");
    if (priceType === "free") {
        return applyGiftPriceDefaults({ ...form, chargeAssetType: "COIN" }, 0);
    }
    if (priceType === "coin") {
        return applyGiftPriceDefaults({ ...form, chargeAssetType: "COIN" }, resource?.coinPrice || 0);
    }
    return form;
}

const emptyGrantForm = () => ({
    durationDays: "7",
    groupId: "",
    quantity: "1",
    reason: "",
    resourceId: "",
    resourceIds: [],
    subjectType: "resource",
    targetUserId: "",
    vipLevel: "",
});

function groupItemToForm(item) {
    const sortOrder = item.sortOrder === 0 || item.sortOrder ? String(item.sortOrder) : "0";
    if (item.itemType === "wallet_asset") {
        return {
            durationDays: "",
            itemType: "wallet_asset",
            resourceId: "",
            sortOrder,
            walletAssetAmount:
                item.walletAssetAmount === 0 || item.walletAssetAmount ? String(item.walletAssetAmount) : "",
            walletAssetType: item.walletAssetType || "",
        };
    }

    return {
        durationDays: item.durationMs ? formatDurationDays(item.durationMs) : "1",
        itemType: "resource",
        resourceId: item.resourceId ? String(item.resourceId) : "",
        sortOrder,
        walletAssetAmount: "",
        walletAssetType: "",
    };
}

export function useResourceListPage() {
    const abilities = useResourceAbilities();
    const confirm = useConfirm();
    const { showToast } = useToast();
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState("");
    const [resourceType, setResourceType] = useState("");
    const [page, setPage] = useState(1);
    const [activeAction, setActiveAction] = useState("");
    const [form, setForm] = useState(emptyResourceForm);
    const [selectedResource, setSelectedResource] = useState(null);
    const [loadingAction, setLoadingAction] = useState("");
    const [resourceRowPatches, setResourceRowPatches] = useState({});
    const [hiddenResourceIds, setHiddenResourceIds] = useState([]);
    const filters = useMemo(
        () => ({
            keyword: query,
            resource_type: resourceType,
            status,
        }),
        [query, resourceType, status],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载资源列表失败",
        fetcher: listResources,
        filters,
        page,
        pageSize,
        queryKey: ["resources", filters, page],
    });
    const hiddenResourceIdSet = useMemo(() => new Set(hiddenResourceIds), [hiddenResourceIds]);
    const resourceData = useMemo(() => {
        const source = result.data || emptyData;
        const sourceItems = Array.isArray(source.items) ? source.items : [];
        const patchedItems = sourceItems
            .map((item) => ({
                ...item,
                ...(resourceRowPatches[item.resourceId] || {}),
            }))
            .filter((item) => !hiddenResourceIdSet.has(item.resourceId));
        return {
            ...source,
            items: patchedItems,
        };
    }, [hiddenResourceIdSet, resourceRowPatches, result.data]);

    useEffect(() => {
        setResourceRowPatches({});
        setHiddenResourceIds([]);
    }, [filters]);

    const patchResourceRow = (resourceId, patch) => {
        if (!resourceId) {
            return;
        }
        setHiddenResourceIds((current) => current.filter((hiddenId) => hiddenId !== resourceId));
        setResourceRowPatches((current) => ({
            ...current,
            [resourceId]: {
                ...(current[resourceId] || {}),
                ...patch,
            },
        }));
    };

    const hideResourceRow = (resourceId) => {
        if (!resourceId) {
            return;
        }
        setHiddenResourceIds((current) => (current.includes(resourceId) ? current : [...current, resourceId]));
    };

    const hideResourceRows = (resourceIds) => {
        const nextIds = resourceIds.filter(Boolean);
        if (!nextIds.length) {
            return;
        }
        setHiddenResourceIds((current) => Array.from(new Set([...current, ...nextIds])));
    };

    const reloadResources = async () => {
        setResourceRowPatches({});
        setHiddenResourceIds([]);
        return result.reload();
    };

    const openCreateResource = () => {
        setForm(emptyResourceForm());
        setSelectedResource(null);
        setActiveAction("create");
    };

    const openEditResource = (resource) => {
        if (!resource?.resourceId) {
            return;
        }
        setForm(emptyResourceForm(resource));
        setSelectedResource(resource);
        setActiveAction("edit");
    };

    const closeAction = () => {
        setActiveAction("");
        setSelectedResource(null);
    };

    const submitResource = async (event) => {
        event.preventDefault();
        const editing = activeAction === "edit";
        await runAction(
            editing ? "resource-edit" : "resource-create",
            editing ? "资源已更新" : "资源已创建",
            async () => {
                const payload = buildResourcePayload(parseForm(resourceFormSchema, form));
                if (editing) {
                    await updateResource(selectedResource.resourceId, payload);
                } else {
                    await createResource(payload);
                }
                setForm(emptyResourceForm());
                closeAction();
                await result.reload();
            },
        );
    };

    const toggleResource = async (resource, nextEnabled = resource.status !== "active") => {
        if (!abilities.canUpdate || !resource?.resourceId) {
            return;
        }
        const nextStatus = nextEnabled ? "active" : "disabled";
        if ((resource.status === "active") === nextEnabled) {
            return;
        }
        await runAction(
            `resource-status-${resource.resourceId}`,
            nextEnabled ? "资源已启用" : "资源已禁用",
            async () => {
                let updatedResource;
                if (nextStatus === "active") {
                    updatedResource = await enableResource(resource.resourceId);
                } else {
                    updatedResource = await disableResource(resource.resourceId);
                }
                if (status && nextStatus !== status) {
                    hideResourceRow(resource.resourceId);
                } else {
                    patchResourceRow(resource.resourceId, {
                        ...(updatedResource || {}),
                        status: updatedResource?.status || nextStatus,
                        updatedAtMs: updatedResource?.updatedAtMs ?? Date.now(),
                    });
                }
            },
        );
    };

    const toggleManagerGrant = async (resource, nextEnabled = !resource.managerGrantEnabled) => {
        if (!abilities.canUpdate || !resource?.resourceId) {
            return;
        }
        if (Boolean(resource.managerGrantEnabled) === nextEnabled) {
            return;
        }
        await runAction(
            `resource-manager-grant-${resource.resourceId}`,
            nextEnabled ? "经理赠送已开启" : "经理赠送已关闭",
            async () => {
                const formValue = parseForm(resourceFormSchema, {
                    ...emptyResourceForm(resource),
                    managerGrantEnabled: nextEnabled,
                });
                const updatedResource = await updateResource(resource.resourceId, buildResourcePayload(formValue));
                patchResourceRow(resource.resourceId, {
                    ...(updatedResource || {}),
                    managerGrantEnabled: updatedResource?.managerGrantEnabled ?? nextEnabled,
                    updatedAtMs: updatedResource?.updatedAtMs ?? Date.now(),
                });
            },
        );
    };

    const deleteResources = async (resources = []) => {
        const targetResources = resources.filter((resource) => resource?.resourceId);
        if (!abilities.canDelete || !targetResources.length) {
            return [];
        }
        const title = targetResources.length > 1 ? "批量删除资源" : "删除资源";
        const message =
            targetResources.length > 1
                ? `确认删除选中的 ${targetResources.length} 个资源？删除成功后资源会从资源列表移除。`
                : `确认删除资源 ${targetResources[0].name || targetResources[0].resourceCode || targetResources[0].resourceId}？删除成功后资源会从资源列表移除。`;
        const ok = await confirm({
            confirmText: "删除",
            message,
            title,
            tone: "danger",
        });
        if (!ok) {
            return [];
        }

        const deletedResourceIds = [];
        setLoadingAction(
            targetResources.length > 1 ? "resource-delete-batch" : `resource-delete-${targetResources[0].resourceId}`,
        );
        try {
            if (targetResources.length > 1) {
                await requestBatchDeleteResources(targetResources.map((resource) => resource.resourceId));
                deletedResourceIds.push(...targetResources.map((resource) => resource.resourceId));
            } else {
                await requestDeleteResource(targetResources[0].resourceId);
                deletedResourceIds.push(targetResources[0].resourceId);
            }
            hideResourceRows(deletedResourceIds);
            showToast(
                targetResources.length > 1 ? `已删除 ${deletedResourceIds.length} 个资源` : "资源已删除",
                "success",
            );
            return deletedResourceIds;
        } catch (err) {
            showToast(err.message || "删除资源失败", "error");
            return [];
        } finally {
            setLoadingAction("");
        }
    };

    const runAction = async (action, successMessage, submitter) => {
        setLoadingAction(action);
        try {
            await submitter();
            showToast(successMessage, "success");
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const resetFilters = () => {
        setQuery("");
        setStatus("");
        setResourceType("");
        setPage(1);
    };

    return {
        ...sharedPageState({ data: resourceData, page, query, reload: reloadResources, result, setPage, setQuery }),
        abilities,
        activeAction,
        changeResourceType: resetSetter(setResourceType, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        deleteResources,
        form,
        loadingAction,
        openCreateResource,
        openEditResource,
        resourceType,
        selectedResource,
        resetFilters,
        setForm,
        status,
        submitResource,
        toggleManagerGrant,
        toggleResource,
    };
}

export function useResourceShopPage() {
    const abilities = useResourceAbilities();
    const { showToast } = useToast();
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState("");
    const [resourceType, setResourceType] = useState("");
    const [page, setPage] = useState(1);
    const [drawerOpen, setDrawerOpen] = useState(false);
    const [purchaseDrawerOpen, setPurchaseDrawerOpen] = useState(false);
    const [purchasePage, setPurchasePage] = useState(1);
    const [purchaseUserFilter, setPurchaseUserFilter] = useState(emptyUserIdentityFilter);
    const [purchaseResourceType, setPurchaseResourceType] = useState("");
    const [purchaseKeyword, setPurchaseKeyword] = useState("");
    const [drawerQuery, setDrawerQuery] = useState("");
    const [drawerResourceType, setDrawerResourceType] = useState("");
    const [resourceOptions, setResourceOptions] = useState([]);
    const [shopItemOptions, setShopItemOptions] = useState([]);
    const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
    const [resourceOptionsError, setResourceOptionsError] = useState("");
    const [resourceOptionsLoadVersion, setResourceOptionsLoadVersion] = useState(0);
    const [draftByResourceId, setDraftByResourceId] = useState({});
    const [loadingAction, setLoadingAction] = useState("");
    const filters = useMemo(
        () => ({
            keyword: query,
            resource_type: resourceType,
            status,
        }),
        [query, resourceType, status],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载道具商店失败",
        fetcher: listResourceShopItems,
        filters,
        page,
        pageSize,
        queryKey: ["resource-shop", filters, page],
    });
    const purchaseFilters = useMemo(
        () => ({
            keyword: purchaseKeyword,
            resource_type: purchaseResourceType,
            ...userIdentityFilterParams(purchaseUserFilter),
        }),
        [purchaseKeyword, purchaseResourceType, purchaseUserFilter],
    );
    const fetchPurchaseOrders = useCallback(
        (requestQuery) => {
            if (!purchaseDrawerOpen) {
                return Promise.resolve(emptyData);
            }
            return listResourceShopPurchaseOrders(requestQuery);
        },
        [purchaseDrawerOpen],
    );
    const purchaseResult = usePaginatedQuery({
        errorMessage: "加载售卖记录失败",
        fetcher: fetchPurchaseOrders,
        filters: purchaseFilters,
        page: purchasePage,
        pageSize,
        queryKey: ["resource-shop-purchase-orders", purchaseDrawerOpen, purchaseFilters, purchasePage],
    });
    const shopItemsByResourceId = useMemo(() => {
        return groupResourceShopItems(shopItemOptions);
    }, [shopItemOptions]);
    const selectedDrafts = useMemo(
        () =>
            Object.values(draftByResourceId)
                .flatMap((draftGroup) => Object.values(draftGroup.specsByDuration || {}))
                .sort(
                    (left, right) =>
                        Number(left.sortOrder || 0) - Number(right.sortOrder || 0) ||
                        Number(left.durationDays || 0) - Number(right.durationDays || 0),
                ),
        [draftByResourceId],
    );
    const filteredResourceOptions = useMemo(() => {
        const keyword = drawerQuery.trim().toLowerCase();
        return resourceOptions.filter((resource) => {
            if (drawerResourceType && resource.resourceType !== drawerResourceType) {
                return false;
            }
            if (!keyword) {
                return true;
            }
            return `${resource.name || ""} ${resource.resourceCode || ""}`.toLowerCase().includes(keyword);
        });
    }, [drawerQuery, drawerResourceType, resourceOptions]);

    useEffect(() => {
        if (!drawerOpen) {
            return undefined;
        }
        let ignore = false;
        setResourceOptionsLoading(true);
        setResourceOptionsError("");
        setResourceOptions([]);
        setShopItemOptions([]);
        // 抽屉同时拉全资源和现有售卖项，避免当前列表分页把同一资源的其他天数规格漏掉或误判为新增。
        Promise.all([
            fetchAllOptionPages(listResources, { status: "active" }),
            fetchAllOptionPages(listResourceShopItems),
        ])
            .then(([resourceData, shopItemData]) => {
                if (!ignore) {
                    setResourceOptions(toShopResourceOptions(resourceData.items || []));
                    setShopItemOptions(shopItemData.items || []);
                }
            })
            .catch((err) => {
                if (!ignore) {
                    const message = err.message || "加载售卖资源失败";
                    // 任一请求失败都不能沿用上次候选；否则“旧资源 + 空规格”会让运营误以为是在新增商品。
                    setResourceOptions([]);
                    setShopItemOptions([]);
                    setResourceOptionsError(message);
                    showToast(message, "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setResourceOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [drawerOpen, resourceOptionsLoadVersion, showToast]);

    const openShopDrawer = () => {
        setDraftByResourceId({});
        setDrawerQuery("");
        setDrawerResourceType("");
        setResourceOptions([]);
        setShopItemOptions([]);
        setResourceOptionsError("");
        setResourceOptionsLoadVersion((current) => current + 1);
        setDrawerOpen(true);
    };

    const reloadShopDrawerOptions = () => {
        if (!drawerOpen) {
            return;
        }
        setDraftByResourceId({});
        setResourceOptionsError("");
        setResourceOptionsLoadVersion((current) => current + 1);
    };

    const closeShopDrawer = () => {
        setDrawerOpen(false);
        setDraftByResourceId({});
        setResourceOptions([]);
        setShopItemOptions([]);
        setResourceOptionsError("");
    };

    const openPurchaseDrawer = () => {
        setPurchaseDrawerOpen(true);
        setPurchasePage(1);
    };

    const closePurchaseDrawer = () => {
        setPurchaseDrawerOpen(false);
    };

    const toggleDraftResource = (resource) => {
        if (!resource?.resourceId) {
            return;
        }
        const resourceId = String(resource.resourceId);
        setDraftByResourceId((current) => {
            if (current[resourceId]) {
                if (!canUnselectResourceShopDraftGroup(current[resourceId])) {
                    return current;
                }
                const next = { ...current };
                delete next[resourceId];
                return next;
            }
            return {
                ...current,
                [resourceId]: buildResourceShopDraftGroup(
                    resource,
                    shopItemsByResourceId.get(Number(resource.resourceId)) || [],
                    Object.keys(current).length,
                ),
            };
        });
    };

    const toggleDraftDuration = (resource, durationDays) => {
        const resourceId = String(resource?.resourceId || "");
        const normalizedDuration = String(durationDays || "");
        if (!resourceId || !normalizedDuration) {
            return;
        }
        setDraftByResourceId((current) => {
            const draftGroup = current[resourceId];
            if (!draftGroup) {
                return current;
            }
            const specsByDuration = { ...draftGroup.specsByDuration };
            if (specsByDuration[normalizedDuration]) {
                if (!canUnselectResourceShopDraftSpec(specsByDuration[normalizedDuration])) {
                    return current;
                }
                delete specsByDuration[normalizedDuration];
            } else {
                // 重新勾选已有规格时恢复其独立价格和主键；真正的新规格才继承资源列表价格。
                const existingItem = (shopItemsByResourceId.get(Number(resourceId)) || []).find(
                    (item) => String(item.durationDays || "1") === normalizedDuration,
                );
                specsByDuration[normalizedDuration] = resourceShopSpecDraft(
                    resource,
                    normalizedDuration,
                    existingItem,
                    Object.keys(current).indexOf(resourceId),
                );
            }
            return {
                ...current,
                [resourceId]: { ...draftGroup, specsByDuration },
            };
        });
    };

    const updateDraftSpec = (resourceId, durationDays, patch) => {
        setDraftByResourceId((current) => {
            const draftGroup = current[resourceId];
            const draft = draftGroup?.specsByDuration?.[durationDays];
            if (!draft) {
                return current;
            }
            return {
                ...current,
                [resourceId]: {
                    ...draftGroup,
                    specsByDuration: {
                        ...draftGroup.specsByDuration,
                        [durationDays]: { ...draft, ...patch },
                    },
                },
            };
        });
    };

    const submitShopItems = async (event) => {
        event.preventDefault();
        if (!abilities.canUpdateShop) {
            return;
        }
        setLoadingAction("resource-shop-upsert");
        try {
            const parsed = parseForm(resourceShopItemsFormSchema, { items: selectedDrafts });
            // 一个资源的每个天数规格都是独立商品行，价格、时间和排序必须逐条提交，不能再按资源合并。
            await upsertResourceShopItems({
                items: parsed.items.map((item) => ({
                    coinPrice: Number(item.coinPrice),
                    durationDays: Number(item.durationDays),
                    effectiveFromMs: Number(item.effectiveFromMs || 0),
                    effectiveToMs: Number(item.effectiveToMs || 0),
                    resourceId: Number(item.resourceId),
                    shopItemId: item.shopItemId ? Number(item.shopItemId) : undefined,
                    sortOrder: Number(item.sortOrder || 0),
                    status: item.status || "active",
                })),
            });
            showToast("售卖资源已保存", "success");
            closeShopDrawer();
            await result.reload();
        } catch (err) {
            showToast(err.message || "保存售卖资源失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const toggleShopItem = async (item, nextEnabled = item.status !== "active") => {
        if (!abilities.canUpdateShop || !item?.shopItemId) {
            return;
        }
        if ((item.status === "active") === nextEnabled) {
            return;
        }
        setLoadingAction(`resource-shop-status-${item.shopItemId}`);
        try {
            if (nextEnabled) {
                await enableResourceShopItem(item.shopItemId);
            } else {
                await disableResourceShopItem(item.shopItemId);
            }
            showToast(nextEnabled ? "售卖资源已启用" : "售卖资源已禁用", "success");
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const resetFilters = () => {
        setQuery("");
        setStatus("");
        setResourceType("");
        setPage(1);
    };

    return {
        ...sharedPageState({ page, query, result, setPage, setQuery }),
        abilities,
        changeDrawerQuery: setDrawerQuery,
        changeDrawerResourceType: setDrawerResourceType,
        changeResourceType: resetSetter(setResourceType, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        closeShopDrawer,
        closePurchaseDrawer,
        drawerOpen,
        drawerQuery,
        drawerResourceType,
        draftByResourceId,
        filteredResourceOptions,
        loadingAction,
        openShopDrawer,
        openPurchaseDrawer,
        purchaseData: purchaseResult.data,
        purchaseDrawerOpen,
        purchaseError: purchaseResult.error,
        purchaseKeyword,
        purchaseLoading: purchaseResult.loading,
        purchasePage,
        purchaseResourceType,
        purchaseUserFilter,
        reloadPurchaseOrders: purchaseResult.reload,
        reloadShopDrawerOptions,
        setPurchasePage,
        resourceOptionsError,
        resourceOptionsLoading,
        resourceType,
        resetFilters,
        selectedDrafts,
        selectedResourceIds: new Set(Object.keys(draftByResourceId)),
        status,
        submitShopItems,
        toggleDraftResource,
        toggleDraftDuration,
        toggleShopItem,
        changePurchaseKeyword: resetSetter(setPurchaseKeyword, setPurchasePage),
        changePurchaseResourceType: resetSetter(setPurchaseResourceType, setPurchasePage),
        changePurchaseUserFilter: resetSetter(setPurchaseUserFilter, setPurchasePage),
        updateDraftSpec,
    };
}

export function useResourceGroupListPage() {
    const abilities = useResourceAbilities();
    const { showToast } = useToast();
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState("");
    const [page, setPage] = useState(1);
    const [activeAction, setActiveAction] = useState("");
    const [form, setForm] = useState(emptyResourceGroupForm);
    const [selectedGroup, setSelectedGroup] = useState(null);
    const [loadingAction, setLoadingAction] = useState("");
    const [resourceOptions, setResourceOptions] = useState([]);
    const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
    const [identityGrantForm, setIdentityGrantForm] = useState(() => emptyResourceIdentityAutoGrantForm());
    const [identityGrantGroupOptions, setIdentityGrantGroupOptions] = useState([]);
    const [identityGrantLoading, setIdentityGrantLoading] = useState(false);
    const filters = useMemo(
        () => ({
            keyword: query,
            status,
        }),
        [query, status],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载资源组列表失败",
        fetcher: listResourceGroups,
        filters,
        page,
        pageSize,
        queryKey: ["resource-groups", filters, page],
    });

    useEffect(() => {
        if (activeAction !== "create" && activeAction !== "edit") {
            return undefined;
        }
        let ignore = false;
        setResourceOptionsLoading(true);
        fetchAllOptionPages(listResources, { status: "active" })
            .then((data) => {
                if (!ignore) {
                    setResourceOptions(toGroupResourceOptions(data.items || [], selectedGroup));
                }
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载资源选项失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setResourceOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [activeAction, selectedGroup, showToast]);

    useEffect(() => {
        if (activeAction !== "identity-config") {
            return undefined;
        }
        let ignore = false;
        setIdentityGrantLoading(true);
        // 这里并行拉取配置和可发放资源组，避免弹窗先显示旧配置再异步换值造成误保存。
        Promise.all([
            getResourceIdentityAutoGrantConfig(),
            fetchAllOptionPages(listResourceGroups, { status: "active" }),
        ])
            .then(([config, groups]) => {
                if (ignore) {
                    return;
                }
                setIdentityGrantForm(emptyResourceIdentityAutoGrantForm(config.items || []));
                setIdentityGrantGroupOptions(mergeResourceGroupOptions(groups.items || [], config.items || []));
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载身份资源组配置失败", "error");
                    setActiveAction("");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setIdentityGrantLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [activeAction, showToast]);

    const openCreateGroup = () => {
        setForm(emptyResourceGroupForm());
        setSelectedGroup(null);
        setActiveAction("create");
    };

    const openEditGroup = (group) => {
        if (!group?.groupId) {
            return;
        }
        setForm(emptyResourceGroupForm(group));
        setSelectedGroup(group);
        setActiveAction("edit");
    };

    const openIdentityGrantConfig = () => {
        setIdentityGrantLoading(true);
        setActiveAction("identity-config");
    };

    const closeAction = () => {
        setActiveAction("");
        setSelectedGroup(null);
    };

    const addResourceItem = () => {
        setForm((current) => ({ ...current, items: [...current.items, emptyGroupResourceItem()] }));
    };

    const addWalletAssetItem = (walletAssetType) => {
        setForm((current) => ({ ...current, items: [...current.items, emptyGroupWalletAssetItem(walletAssetType)] }));
    };

    const updateGroupItem = (index, patch) => {
        setForm((current) => ({
            ...current,
            items: current.items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)),
        }));
    };

    const removeGroupItem = (index) => {
        setForm((current) => ({
            ...current,
            items: current.items.filter((_, itemIndex) => itemIndex !== index),
        }));
    };

    const submitGroup = async (event) => {
        event.preventDefault();
        const editing = activeAction === "edit";
        const action = editing ? "group-edit" : "group-create";
        const successMessage = editing ? "资源组已更新" : "资源组已创建";
        setLoadingAction(action);
        try {
            const payload = buildResourceGroupPayload(parseForm(resourceGroupCreateFormSchema, form));
            if (editing) {
                await updateResourceGroup(selectedGroup.groupId, payload);
            } else {
                await createResourceGroup(payload);
            }
            showToast(successMessage, "success");
            setForm(emptyResourceGroupForm());
            closeAction();
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const submitIdentityGrantConfig = async (event) => {
        event.preventDefault();
        if (!abilities.canUpdateGroup) {
            return;
        }
        setLoadingAction("identity-grant-config");
        try {
            const payload = buildResourceIdentityAutoGrantPayload(
                parseForm(resourceIdentityAutoGrantFormSchema, { items: identityGrantForm }),
            );
            const nextConfig = await updateResourceIdentityAutoGrantConfig(payload);
            setIdentityGrantForm(emptyResourceIdentityAutoGrantForm(nextConfig.items || []));
            showToast("身份资源组配置已保存", "success");
            closeAction();
        } catch (err) {
            showToast(err.message || "保存身份资源组配置失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const toggleGroup = async (group, nextEnabled = group.status !== "active") => {
        if (!abilities.canUpdateGroup || !group?.groupId) {
            return;
        }
        if ((group.status === "active") === nextEnabled) {
            return;
        }
        setLoadingAction(`group-status-${group.groupId}`);
        try {
            if (nextEnabled) {
                await enableResourceGroup(group.groupId);
            } else {
                await disableResourceGroup(group.groupId);
            }
            showToast(nextEnabled ? "资源组已启用" : "资源组已禁用", "success");
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const resetFilters = () => {
        setQuery("");
        setStatus("");
        setPage(1);
    };

    return {
        ...sharedPageState({ page, query, result, setPage, setQuery }),
        abilities,
        activeAction,
        addResourceItem,
        addWalletAssetItem,
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        form,
        identityGrantForm,
        identityGrantGroupOptions,
        identityGrantLoading,
        loadingAction,
        openCreateGroup,
        openEditGroup,
        openIdentityGrantConfig,
        removeGroupItem,
        resourceOptions,
        resourceOptionsLoading,
        selectedGroup,
        resetFilters,
        setForm,
        setIdentityGrantForm,
        status,
        submitIdentityGrantConfig,
        submitGroup,
        toggleGroup,
        updateGroupItem,
    };
}

export function useGiftListPage() {
    const abilities = useResourceAbilities();
    const confirm = useConfirm();
    const { showToast } = useToast();
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState("");
    const [regionId, setRegionId] = useState("");
    const [giftTypeCode, setGiftTypeCode] = useState("");
    const [page, setPage] = useState(1);
    const [activeAction, setActiveAction] = useState("");
    const [form, setForm] = useState(emptyGiftForm);
    const [selectedGift, setSelectedGift] = useState(null);
    const [loadingAction, setLoadingAction] = useState("");
    const [giftSortSavingIds, setGiftSortSavingIds] = useState({});
    const [giftRowPatches, setGiftRowPatches] = useState({});
    const [giftTypes, setGiftTypes] = useState(defaultGiftTypeOptions);
    const [giftTypeForm, setGiftTypeForm] = useState({ items: defaultGiftTypeOptions.map(giftTypeToForm) });
    const [giftTypeDialogOpen, setGiftTypeDialogOpen] = useState(false);
    const [giftTypesLoading, setGiftTypesLoading] = useState(false);
    const [resourceOptions, setResourceOptions] = useState([]);
    const [giftResourceGiftOptions, setGiftResourceGiftOptions] = useState([]);
    const [existingGiftResourceIds, setExistingGiftResourceIds] = useState([]);
    const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
    const { loadingRegions, regionOptions } = useRegionOptions();
    const filters = useMemo(
        () => ({
            gift_type_code: giftTypeCode,
            keyword: query,
            region_id: regionId,
            status,
        }),
        [giftTypeCode, query, regionId, status],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载礼物列表失败",
        fetcher: listGifts,
        filters,
        page,
        pageSize,
        queryKey: ["gifts", filters, page],
    });
    const giftData = useMemo(() => {
        const source = result.data || emptyData;
        const sourceItems = Array.isArray(source.items) ? source.items : [];
        return {
            ...source,
            items: sourceItems.map((item) => ({
                ...item,
                ...(giftRowPatches[item.giftId] || {}),
            })),
        };
    }, [giftRowPatches, result.data]);

    useEffect(() => {
        let ignore = false;
        setGiftTypesLoading(true);
        listGiftTypes()
            .then((items = []) => {
                if (ignore) {
                    return;
                }
                const nextItems = mergeGiftTypeOptions(items);
                setGiftTypes(nextItems);
                setGiftTypeForm({ items: nextItems.map(giftTypeToForm) });
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载礼物类型失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setGiftTypesLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [showToast]);

    useEffect(() => {
        if (activeAction !== "create" && activeAction !== "edit") {
            return undefined;
        }
        let ignore = false;
        setResourceOptionsLoading(true);
        const optionRequests =
            activeAction === "create"
                ? [
                      fetchAllOptionPages(listResources, { resource_type: "gift" }),
                      fetchAllOptionPages(listGifts, {}, optionPageSize, 1000),
                  ]
                : [fetchAllOptionPages(listResources, { resource_type: "gift" }), Promise.resolve({ items: [] })];
        Promise.all(optionRequests)
            .then(([resourceData, giftOptionsData]) => {
                if (!ignore) {
                    const giftOptions = giftOptionsData.items || [];
                    setGiftResourceGiftOptions(giftOptions);
                    setExistingGiftResourceIds(
                        giftOptions
                            .map((gift) => gift?.resourceId)
                            .filter((resourceId) => resourceId || resourceId === 0)
                            .map(String),
                    );
                    setResourceOptions(toGiftResourceOptions(resourceData.items || [], selectedGift));
                }
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载礼物资源失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setResourceOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [activeAction, selectedGift, showToast]);

    const openCreateGift = () => {
        setForm(emptyGiftForm());
        setSelectedGift(null);
        setActiveAction("create");
    };

    const openEditGift = (gift) => {
        if (!gift?.giftId) {
            return;
        }
        setForm(emptyGiftForm(gift));
        setSelectedGift(gift);
        setActiveAction("edit");
    };

    const closeAction = () => {
        setActiveAction("");
        setSelectedGift(null);
    };

    const selectGiftResource = (resourceId) => {
        const selectedResource = resourceOptions.find((resource) => String(resource.resourceId) === String(resourceId));
        setForm((currentForm) => {
            return applyGiftResourceSelection(
                currentForm,
                selectedResource || { resourceId },
                activeAction === "create",
            );
        });
    };

    const selectGiftResources = (resourceIds) => {
        setForm((currentForm) => {
            const items = buildGiftResourceDrafts(currentForm, resourceIds, resourceOptions);
            return {
                ...currentForm,
                items,
                resourceId: items[0]?.resourceId || "",
            };
        });
    };

    const updateGiftDraft = (resourceId, patch) => {
        setForm((currentForm) => ({
            ...currentForm,
            items: (currentForm.items || []).map((item) => {
                if (String(item.resourceId) !== String(resourceId)) {
                    return item;
                }
                const nextItem = { ...item, ...patch };
                if (Object.prototype.hasOwnProperty.call(patch, "giftTypeCode")) {
                    const cpRelationType =
                        nextItem.giftTypeCode === cpGiftTypeCode
                            ? nextItem.cpRelationType || defaultCPRelationType
                            : "";
                    return {
                        ...nextItem,
                        cpRelationType,
                        presentationJson: normalizeGiftPresentationJson(
                            nextItem.presentationJson,
                            nextItem.giftTypeCode,
                            cpRelationType,
                        ),
                    };
                }
                if (Object.prototype.hasOwnProperty.call(patch, "cpRelationType")) {
                    return {
                        ...nextItem,
                        presentationJson: normalizeGiftPresentationJson(
                            nextItem.presentationJson,
                            nextItem.giftTypeCode,
                            nextItem.cpRelationType,
                        ),
                    };
                }
                return nextItem;
            }),
        }));
    };

    const removeGiftDraft = (resourceId) => {
        setForm((currentForm) => {
            const items = (currentForm.items || []).filter((item) => String(item.resourceId) !== String(resourceId));
            return {
                ...currentForm,
                items,
                resourceId: items[0]?.resourceId || "",
            };
        });
    };

    const changeGiftPrice = (coinPrice) => {
        setForm((currentForm) => applyGiftPriceDefaults(currentForm, coinPrice));
    };

    const changeGiftFormTypeCode = (nextGiftTypeCode) => {
        setForm((currentForm) => {
            const cpRelationType =
                nextGiftTypeCode === cpGiftTypeCode
                    ? currentForm.cpRelationType || cpRelationTypeFromPresentationJson(currentForm.presentationJson)
                    : "";
            return {
                ...currentForm,
                cpRelationType: cpRelationType || (nextGiftTypeCode === cpGiftTypeCode ? defaultCPRelationType : ""),
                giftTypeCode: nextGiftTypeCode,
                presentationJson: normalizeGiftPresentationJson(
                    currentForm.presentationJson,
                    nextGiftTypeCode,
                    cpRelationType,
                ),
            };
        });
    };

    const changeGiftCPRelationType = (nextCPRelationType) => {
        setForm((currentForm) => ({
            ...currentForm,
            cpRelationType: nextCPRelationType,
            presentationJson: normalizeGiftPresentationJson(
                currentForm.presentationJson,
                currentForm.giftTypeCode,
                nextCPRelationType,
            ),
        }));
    };

    const openGiftTypeDialog = () => {
        setGiftTypeForm({ items: giftTypes.map(giftTypeToForm) });
        setGiftTypeDialogOpen(true);
    };

    const closeGiftTypeDialog = () => {
        setGiftTypeDialogOpen(false);
    };

    const updateGiftTypeFormItem = (index, patch) => {
        setGiftTypeForm((current) => ({
            ...current,
            items: current.items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)),
        }));
    };

    const submitGiftTypes = async (event) => {
        event.preventDefault();
        if (!abilities.canUpdateGift) {
            return;
        }
        setLoadingAction("gift-types");
        try {
            const parsed = parseForm(giftTypeConfigFormSchema, giftTypeForm);
            const savedItems = await updateGiftTypes({
                items: parsed.items.map((item) => ({
                    tabKey: item.tabKey,
                    ...buildGiftTypePayload(item),
                })),
            });
            const nextItems = mergeGiftTypeOptions(savedItems);
            setGiftTypes(nextItems);
            setGiftTypeForm({ items: nextItems.map(giftTypeToForm) });
            showToast("礼物类型已保存", "success");
            setGiftTypeDialogOpen(false);
        } catch (err) {
            showToast(err.message || "保存礼物类型失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const submitGift = async (event) => {
        event.preventDefault();
        const editing = activeAction === "edit";
        if (editing && !selectedGift?.giftId) {
            return;
        }
        setLoadingAction(editing ? "gift-edit" : "gift-create");
        const createdDrafts = [];
        let shouldReload = false;
        try {
            const createDrafts = !editing && Array.isArray(form.items) && form.items.length ? form.items : [form];
            if (editing) {
                const payload = buildGiftPayload(parseForm(giftFormSchema, form));
                await updateGift(selectedGift.giftId, payload);
            } else {
                const payloads = createDrafts.map((draft) => ({
                    draft,
                    payload: buildGiftPayload(parseForm(giftFormSchema, draft)),
                }));
                if (payloads.length > 1) {
                    await batchCreateGifts(payloads.map(({ payload }) => payload));
                    createdDrafts.push(...payloads.map(({ draft }) => draft));
                } else {
                    await createGift(payloads[0].payload);
                    createdDrafts.push(payloads[0].draft);
                }
            }
            showToast(editing ? "礼物已更新" : `已创建 ${createDrafts.length} 个礼物`, "success");
            closeAction();
            shouldReload = true;
        } catch (err) {
            if (!editing && createdDrafts.length) {
                const createdResourceIds = new Set(createdDrafts.map((draft) => String(draft.resourceId)));
                setExistingGiftResourceIds((current) => Array.from(new Set([...current, ...createdResourceIds])));
                setForm((currentForm) => {
                    const items = (currentForm.items || []).filter(
                        (item) => !createdResourceIds.has(String(item.resourceId)),
                    );
                    return {
                        ...currentForm,
                        items,
                        resourceId: items[0]?.resourceId || "",
                    };
                });
                shouldReload = true;
                showToast(
                    `已创建 ${createdDrafts.length} 个礼物，剩余礼物创建失败：${err.message || "操作失败"}`,
                    "error",
                );
            } else {
                showToast(err.message || "操作失败", "error");
            }
        } finally {
            setLoadingAction("");
        }
        if (shouldReload) {
            try {
                await result.reload();
            } catch (err) {
                showToast(err.message || "刷新礼物列表失败", "error");
            }
        }
    };

    const toggleGift = async (gift, nextEnabled = gift.status !== "active") => {
        if (!abilities.canStatusGift || !gift?.giftId) {
            return;
        }
        if ((gift.status === "active") === nextEnabled) {
            return;
        }
        setLoadingAction(`gift-status-${gift.giftId}`);
        try {
            if (nextEnabled) {
                await enableGift(gift.giftId);
            } else {
                await disableGift(gift.giftId);
            }
            showToast(nextEnabled ? "礼物已启用" : "礼物已禁用", "success");
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const deleteGifts = async (gifts = []) => {
        const targetGifts = gifts.filter((gift) => gift?.giftId);
        if (!abilities.canDeleteGift || !targetGifts.length) {
            return false;
        }
        const title = targetGifts.length > 1 ? "批量删除礼物" : "删除礼物";
        const message =
            targetGifts.length > 1
                ? `确认删除选中的 ${targetGifts.length} 个礼物？只删除礼物列表数据，不删除资源列表数据。`
                : `确认删除礼物 ${targetGifts[0].name || targetGifts[0].giftId}？只删除礼物列表数据，不删除资源列表数据。`;
        const ok = await confirm({
            confirmText: "删除",
            message,
            title,
            tone: "danger",
        });
        if (!ok) {
            return false;
        }
        setLoadingAction(targetGifts.length > 1 ? "gift-delete-batch" : `gift-delete-${targetGifts[0].giftId}`);
        try {
            for (const gift of targetGifts) {
                await requestDeleteGift(gift.giftId);
            }
            showToast(targetGifts.length > 1 ? `已删除 ${targetGifts.length} 个礼物` : "礼物已删除", "success");
            await result.reload();
            return true;
        } catch (err) {
            showToast(err.message || "删除礼物失败", "error");
            return false;
        } finally {
            setLoadingAction("");
        }
    };

    const saveGiftSortOrder = async (gift, rawSortOrder) => {
        if (!abilities.canUpdateGift || !gift?.giftId) {
            return false;
        }
        const sortOrder = Number(rawSortOrder);
        if (!Number.isInteger(sortOrder) || sortOrder < 0) {
            showToast("排序必须是大于等于 0 的整数", "error");
            return false;
        }
        if (sortOrder === Number(gift.sortOrder || 0)) {
            return true;
        }
        setGiftSortSavingIds((current) => ({ ...current, [gift.giftId]: true }));
        try {
            await updateGift(gift.giftId, buildGiftInlineSortPayload(gift, sortOrder));
            setGiftRowPatches((current) => ({
                ...current,
                [gift.giftId]: {
                    ...(current[gift.giftId] || {}),
                    sortOrder,
                },
            }));
            showToast("排序已保存", "success");
            return true;
        } catch (err) {
            showToast(err.message || "保存排序失败", "error");
            return false;
        } finally {
            setGiftSortSavingIds((current) => {
                const next = { ...current };
                delete next[gift.giftId];
                return next;
            });
        }
    };

    const resetFilters = () => {
        setQuery("");
        setStatus("");
        setRegionId("");
        setGiftTypeCode("");
        setPage(1);
    };

    return {
        ...sharedPageState({ page, query, result, setPage, setQuery }),
        data: giftData,
        abilities,
        activeAction,
        changeGiftCPRelationType,
        changeGiftPrice,
        changeGiftFormTypeCode,
        changeRegionId: resetSetter(setRegionId, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        changeGiftTypeCode: resetSetter(setGiftTypeCode, setPage),
        closeAction,
        closeGiftTypeDialog,
        deleteGifts,
        form,
        giftSortSavingIds,
        giftTypeCode,
        giftTypeDialogOpen,
        giftTypeForm,
        giftTypeOptions: giftTypes,
        giftTypesLoading,
        giftResourceGiftOptions,
        existingGiftResourceIds,
        loadingAction,
        loadingRegions,
        openCreateGift,
        openEditGift,
        openGiftTypeDialog,
        regionId,
        regionOptions,
        resourceOptions,
        resourceOptionsLoading,
        selectedGift,
        resetFilters,
        selectGiftResource,
        selectGiftResources,
        removeGiftDraft,
        setForm,
        saveGiftSortOrder,
        submitGiftTypes,
        status,
        submitGift,
        toggleGift,
        updateGiftDraft,
        updateGiftTypeFormItem,
    };
}

export function useEmojiPackListPage() {
    const abilities = useResourceAbilities();
    const { showToast } = useToast();
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState("");
    const [page, setPage] = useState(1);
    const [activeAction, setActiveAction] = useState("");
    const [form, setForm] = useState(emptyEmojiPackForm);
    const [loadingAction, setLoadingAction] = useState("");
    const [categoryOptions, setCategoryOptions] = useState([]);
    const [categoryOptionsLoading, setCategoryOptionsLoading] = useState(false);
    const { loadingRegions, regionOptions } = useRegionOptions();
    const filters = useMemo(
        () => ({
            keyword: query,
            status,
        }),
        [query, status],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载表情包列表失败",
        fetcher: listEmojiPacks,
        filters,
        page,
        pageSize,
        queryKey: ["emoji-packs", filters, page],
    });

    useEffect(() => {
        let ignore = false;
        setCategoryOptionsLoading(true);
        listEmojiPackCategories()
            .then((options) => {
                if (!ignore) {
                    setCategoryOptions(mergeEmojiPackCategoryOptions(options));
                }
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载表情包分类失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setCategoryOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [showToast]);

    const openCreateEmojiPack = () => {
        setForm(emptyEmojiPackForm());
        setActiveAction("create");
    };

    const closeAction = () => {
        setActiveAction("");
    };

    const submitEmojiPack = async (event) => {
        event.preventDefault();
        if (!abilities.canCreateEmojiPack) {
            return;
        }
        setLoadingAction("emoji-pack-create");
        try {
            const payload = buildEmojiPackPayload(parseForm(emojiPackFormSchema, form));
            await createEmojiPack(payload);
            setCategoryOptions((options) => mergeEmojiPackCategoryOptions(options, payload.category));
            showToast("表情包已创建", "success");
            closeAction();
            setForm(emptyEmojiPackForm());
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const toggleEmojiPack = async (emojiPack, nextEnabled = emojiPack.status !== "active") => {
        if (!abilities.canUpdate || !emojiPack?.resourceId) {
            return;
        }
        if ((emojiPack.status === "active") === nextEnabled) {
            return;
        }
        setLoadingAction(`emoji-pack-status-${emojiPack.resourceId}`);
        try {
            if (nextEnabled) {
                await enableResource(emojiPack.resourceId);
            } else {
                await disableResource(emojiPack.resourceId);
            }
            showToast(nextEnabled ? "表情包已启用" : "表情包已禁用", "success");
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const resetFilters = () => {
        setQuery("");
        setStatus("");
        setPage(1);
    };

    return {
        ...sharedPageState({ page, query, result, setPage, setQuery }),
        abilities,
        activeAction,
        categoryOptions,
        categoryOptionsLoading,
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        form,
        loadingAction,
        loadingRegions,
        openCreateEmojiPack,
        regionOptions,
        resetFilters,
        setForm,
        status,
        submitEmojiPack,
        toggleEmojiPack,
    };
}

export function useResourceGrantListPage() {
    const abilities = useResourceAbilities();
    const confirm = useConfirm();
    const { showToast } = useToast();
    const [targetFilter, setTargetFilter] = useState(emptyUserIdentityFilter);
    const [status, setStatus] = useState("");
    const [page, setPage] = useState(1);
    const [activeAction, setActiveAction] = useState("");
    const [form, setForm] = useState(emptyGrantForm);
    const [giftOptions, setGiftOptions] = useState([]);
    const [giftTypeOptions, setGiftTypeOptions] = useState(defaultGiftTypeOptions);
    const [groupOptions, setGroupOptions] = useState([]);
    const [resourceOptions, setResourceOptions] = useState([]);
    const [vipProgram, setVipProgram] = useState({ programType: "legacy_timed", trialCardEnabled: false });
    const [vipLevelOptions, setVipLevelOptions] = useState([]);
    const [optionsLoading, setOptionsLoading] = useState(false);
    const [loadingAction, setLoadingAction] = useState("");
    const filters = useMemo(
        () => ({
            status,
            ...userIdentityFilterParams(targetFilter, {
                displayUserId: "target_display_user_id",
                userId: "target_user_id",
                username: "target_username",
            }),
        }),
        [status, targetFilter],
    );
    const result = usePaginatedQuery({
        errorMessage: "加载资源赠送记录失败",
        fetcher: listResourceGrants,
        filters,
        page,
        pageSize,
        queryKey: ["resource-grants", filters, page],
    });

    useEffect(() => {
        if (activeAction !== "create") {
            return undefined;
        }
        let ignore = false;
        setOptionsLoading(true);
        const optionRequests = [];
        if (abilities.canCreateGrant) {
            optionRequests.push(loadResourceGrantOptions().then((data) => ({ data, type: "resource" })));
        } else {
            setResourceOptions([]);
            setGiftOptions([]);
            setGiftTypeOptions(defaultGiftTypeOptions);
            setGroupOptions([]);
        }
        if (abilities.canGrantVip) {
            optionRequests.push(
                Promise.all([getVipProgram(), getVipConfig()]).then(([program, config]) => ({
                    data: { config, program },
                    type: "vip",
                })),
            );
        } else {
            setVipProgram({ programType: "legacy_timed", trialCardEnabled: false });
            setVipLevelOptions([]);
        }
        Promise.allSettled(optionRequests)
            .then((results) => {
                if (ignore) {
                    return;
                }
                results.forEach((result) => {
                    if (result.status === "rejected") {
                        showToast(result.reason?.message || "加载资源选项失败", "error");
                        return;
                    }
                    if (result.value.type === "resource") {
                        const { gifts, giftTypes, groups, resources } = result.value.data;
                        setResourceOptions(
                            (resources.items || []).filter(
                                (resource) =>
                                    resource.status !== "disabled" &&
                                    resource.grantable !== false &&
                                    // Fami 体验卡必须走专用发卡命令，不能从通用资源分支绕过 level/duration 校验。
                                    resource.resourceType !== "vip_trial_card",
                            ),
                        );
                        setGiftOptions(
                            (gifts.items || []).filter((gift) => gift.resourceId && gift.status !== "disabled"),
                        );
                        setGiftTypeOptions(mergeGiftTypeOptions(giftTypes));
                        setGroupOptions(groups.items || []);
                        return;
                    }
                    const { config, program } = result.value.data;
                    const activeVipLevels = (config.levels || []).filter((level) => level.status === "active");
                    const isTrialCardProgram = usesVipTrialCardGrant(program);
                    setVipProgram(program);
                    setVipLevelOptions(activeVipLevels);
                    setForm((current) => {
                        if (!isVipGrantSubject(current.subjectType)) {
                            return current;
                        }
                        const subjectType = isTrialCardProgram ? "vip_trial_card" : "vip";
                        const vipLevel = current.vipLevel || String(activeVipLevels[0]?.level || "");
                        return {
                            ...current,
                            subjectType,
                            vipLevel,
                        };
                    });
                });
            })
            .finally(() => {
                if (!ignore) {
                    setOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [abilities.canCreateGrant, abilities.canGrantVip, activeAction, showToast]);

    const openCreateGrant = () => {
        setForm({ ...emptyGrantForm(), subjectType: defaultGrantSubjectType(abilities) });
        setActiveAction("create");
    };

    const closeAction = () => {
        setActiveAction("");
    };

    const submitGrant = async (event) => {
        event.preventDefault();
        if (!abilities.canCreateGrant && !abilities.canGrantVip) {
            return;
        }
        setLoadingAction("grant-create");
        try {
            const parsedForm = parseForm(resourceGrantFormSchema, form);
            if (!canSubmitGrantSubject(parsedForm.subjectType, abilities)) {
                return;
            }
            const targetUser = await lookupResourceGrantTarget(parsedForm.targetUserId);
            const targetUserId = normalizedResolvedUserId(targetUser);
            const payload = buildGrantPayload(parsedForm, targetUserId);
            if (payload.subjectType === "resource") {
                for (const resourcePayload of payload.resources) {
                    await grantResource(resourcePayload);
                }
            } else if (payload.subjectType === "resource_group") {
                await grantResourceGroup(payload.group);
            } else if (payload.subjectType === "vip") {
                await grantVip(payload.vip);
            } else {
                await grantVipTrialCard(payload.vipTrialCard);
            }
            showToast(
                payload.subjectType === "vip"
                    ? "VIP已赠送"
                    : payload.subjectType === "vip_trial_card"
                      ? "VIP体验卡已赠送"
                      : "资源已赠送",
                "success",
            );
            closeAction();
            setForm(emptyGrantForm());
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const revokeGrant = async (grant) => {
        if (!abilities.canRevokeGrant || !grant?.grantId || grant.status !== "succeeded" || !isRevocableGrant(grant)) {
            return;
        }
        const accepted = await confirm({
            title: "撤销资源赠送",
            message:
                "撤销后会按原发放内容扣回资源：素材扣减对应有效天数，金币扣减对应数量，最低扣至 0。该操作不可自动恢复。",
            confirmText: "撤销",
            tone: "danger",
        });
        if (!accepted) {
            return;
        }
        setLoadingAction(`grant-revoke-${grant.grantId}`);
        try {
            await revokeResourceGrant(grant.grantId);
            showToast("资源赠送已撤销", "success");
            await result.reload();
        } catch (err) {
            showToast(err.message || "撤销失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    const resetFilters = () => {
        setTargetFilter(emptyUserIdentityFilter);
        setStatus("");
        setPage(1);
    };

    return {
        abilities,
        activeAction,
        changeTargetFilter: resetSetter(setTargetFilter, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        data: result.data || emptyData,
        error: result.error,
        form,
        giftOptions,
        giftTypeOptions,
        groupOptions,
        loading: result.loading,
        loadingAction,
        openCreateGrant,
        optionsLoading,
        page,
        reload: result.reload,
        resourceOptions,
        resetFilters,
        revokeGrant,
        setForm,
        setPage,
        status,
        submitGrant,
        targetFilter,
        vipProgram,
        vipLevelOptions,
    };
}

function sharedPageState({ data, page, query, reload, result, setPage, setQuery }) {
    return {
        data: data || result.data || emptyData,
        error: result.error,
        loading: result.loading,
        page,
        query,
        reload: reload || result.reload,
        setPage,
        changeQuery: resetSetter(setQuery, setPage),
    };
}

function resetSetter(setValue, setPage) {
    return (value) => {
        setValue(value);
        setPage(1);
    };
}

function buildResourcePayload(form) {
    const resourceType = form.resourceType;
    const isCoin = resourceType === "coin";
    const isLevelAvatarFrame = resourceType === "avatar_frame" && form.avatarFrameKind === "level";
    const isLevelBadge = resourceType === "badge" && form.badgeKind === "level";
    const animationUrl = form.animationUrl.trim();
    let metadataJson = form.metadataJson;
    if (resourceRequiresExplicitVisualFormat(resourceType)) {
        metadataJson = roomBorderFormatMetadataJSON(metadataJson, form.roomBorderFormat);
    }
    if (resourceType === "room_name_style") {
        metadataJson = roomNameStyleMetadataJSON(metadataJson, form);
    }
    return {
        amount: isCoin ? Number(form.walletAssetAmount) : 0,
        animationUrl,
        assetUrl: animationUrl,
        avatarFrameKind: resourceType === "avatar_frame" ? form.avatarFrameKind || "normal" : undefined,
        badgeForm: resourceType === "badge" ? form.badgeForm : undefined,
        badgeKind: resourceType === "badge" ? form.badgeKind || "normal" : undefined,
        coinPrice: form.priceType === "coin" ? Number(form.coinPrice) : 0,
        levelTrack: isLevelBadge || isLevelAvatarFrame ? form.levelTrack : undefined,
        managerGrantEnabled: Boolean(form.managerGrantEnabled),
        metadataJson: resourceMetadataJSON(resourceType, metadataJson),
        name: form.name.trim(),
        previewUrl: form.previewUrl.trim(),
        priceType: form.priceType,
        resourceCode: form.resourceCode.trim(),
        resourceType,
        sortOrder: 0,
        status: form.enabled ? "active" : "disabled",
    };
}

function badgeFormFromMetadata(metadataJson) {
    if (!metadataJson) {
        return "";
    }
    try {
        const metadata = JSON.parse(metadataJson);
        const badgeForm = String(metadata?.badge_form || "")
            .trim()
            .toLowerCase();
        if (badgeForm === "strip" || badgeForm === "tile") {
            return badgeForm;
        }
    } catch {
        return "";
    }
    return "";
}

function badgeKindFromMetadata(metadataJson) {
    if (!metadataJson) {
        return "";
    }
    try {
        const metadata = JSON.parse(metadataJson);
        const badgeKind = String(metadata?.badge_kind || "")
            .trim()
            .toLowerCase();
        if (badgeKind === "level") {
            return "level";
        }
    } catch {
        return "";
    }
    return "";
}

function avatarFrameKindFromMetadata(metadataJson) {
    if (!metadataJson) {
        return "";
    }
    try {
        const metadata = JSON.parse(metadataJson);
        const avatarFrameKind = String(metadata?.avatar_frame_kind || "")
            .trim()
            .toLowerCase();
        if (avatarFrameKind === "level" || avatarFrameKind === "normal") {
            return avatarFrameKind;
        }
    } catch {
        return "";
    }
    return "";
}

function levelTrackFromMetadata(metadataJson) {
    if (!metadataJson) {
        return "";
    }
    try {
        const metadata = JSON.parse(metadataJson);
        const levelTrack = String(metadata?.level_track || "")
            .trim()
            .toLowerCase();
        if (levelTrack === "wealth" || levelTrack === "game" || levelTrack === "charm") {
            return levelTrack;
        }
    } catch {
        return "";
    }
    return "";
}

function buildEmojiPackPayload(form) {
    const regionIds = (form.regionIds || [])
        .map(Number)
        .filter((regionId) => Number.isInteger(regionId) && regionId >= 0);
    return {
        animationUrl: form.animationUrl.trim(),
        category: form.category.trim(),
        coverUrl: form.coverUrl.trim(),
        name: form.name.trim(),
        pricingType: form.pricingType,
        regionIds: regionIds.length ? regionIds : [0],
        sortOrder: Number(form.sortOrder || 0),
    };
}

function mergeEmojiPackCategoryOptions(options = [], value = "默认") {
    const merged = new Set(["默认"]);
    options.forEach((option) => {
        const category = String(option || "").trim();
        if (category) {
            merged.add(category);
        }
    });
    const category = String(value || "").trim();
    if (category) {
        merged.add(category);
    }
    return Array.from(merged).sort((left, right) => left.localeCompare(right, "zh-Hans-CN"));
}

function buildResourceGroupPayload(form) {
    return {
        description: (form.description || "").trim(),
        groupCode: form.groupCode.trim(),
        items: form.items.map((item, index) => {
            const sortOrder = Number(item.sortOrder || index);
            if (item.itemType === "wallet_asset") {
                return {
                    amount: Number(item.walletAssetAmount),
                    assetType: item.walletAssetType,
                    itemType: "wallet_asset",
                    sortOrder: Number.isInteger(sortOrder) ? sortOrder : index,
                };
            }
            return {
                durationDays: Number(item.durationDays),
                itemType: "resource",
                resourceId: Number(item.resourceId),
                sortOrder: Number.isInteger(sortOrder) ? sortOrder : index,
            };
        }),
        name: form.name.trim(),
        sortOrder: Number(form.sortOrder || 0),
        status: form.enabled ? "active" : "disabled",
    };
}

function buildResourceIdentityAutoGrantPayload(form) {
    const itemByIdentityType = new Map(form.items.map((item) => [item.identityType, item]));
    return {
        items: resourceIdentityAutoGrantOptions.map(([identityType]) => {
            const resourceGroupId = Number(itemByIdentityType.get(identityType)?.resourceGroupId || 0);
            return {
                identity_type: identityType,
                resource_group_id: Number.isInteger(resourceGroupId) && resourceGroupId >= 0 ? resourceGroupId : 0,
            };
        }),
    };
}

function mergeResourceGroupOptions(activeGroups = [], configItems = []) {
    const groupById = new Map();
    activeGroups.forEach((group) => {
        if (group?.groupId) {
            groupById.set(String(group.groupId), group);
        }
    });
    configItems.forEach((item) => {
        const group = item?.resourceGroup;
        if (group?.groupId && !groupById.has(String(group.groupId))) {
            groupById.set(String(group.groupId), group);
        }
    });
    return Array.from(groupById.values());
}

function buildGiftPayload(form) {
    const cpRelationType = form.giftTypeCode === cpGiftTypeCode ? normalizedCPRelationType(form.cpRelationType) : "";
    return {
        chargeAssetType: form.chargeAssetType,
        coinPrice: Number(form.coinPrice),
        cpRelationType,
        effectiveAtMs: Date.now(),
        effectiveFromMs: datetimeLocalToMs(form.effectiveFrom),
        effectiveToMs: datetimeLocalToMs(form.effectiveTo),
        effectTypes: form.effectTypes || [],
        giftId: form.giftId.trim(),
        giftTypeCode: form.giftTypeCode,
        name: form.name.trim(),
        presentationJson: normalizeGiftPresentationJson(form.presentationJson, form.giftTypeCode, form.cpRelationType),
        priceVersion: form.priceVersion.trim(),
        regionIds: form.regionIds.map(Number),
        resourceId: Number(form.resourceId),
        sortOrder: Number(form.sortOrder || 0),
        status: form.enabled ? "active" : "disabled",
    };
}

function buildGiftInlineSortPayload(gift, sortOrder) {
    const cpRelationType = cpRelationTypeFromGift(gift);
    return {
        chargeAssetType: gift.chargeAssetType || "COIN",
        coinPrice: Number(gift.coinPrice || 0),
        cpRelationType: gift.giftTypeCode === cpGiftTypeCode ? cpRelationType || defaultCPRelationType : "",
        effectiveAtMs: Date.now(),
        effectiveFromMs: Number(gift.effectiveFromMs || 0),
        effectiveToMs: Number(gift.effectiveToMs || 0),
        effectTypes: Array.isArray(gift.effectTypes) ? gift.effectTypes : [],
        giftId: String(gift.giftId || "").trim(),
        giftTypeCode: gift.giftTypeCode || "normal",
        name: String(gift.name || gift.resource?.name || gift.giftId || "").trim(),
        presentationJson: String(gift.presentationJson || "{}").trim() || "{}",
        priceVersion: String(gift.priceVersion || "default").trim(),
        regionIds: Array.isArray(gift.regionIds) && gift.regionIds.length ? gift.regionIds.map(Number) : [0],
        resourceId: Number(gift.resourceId || 0),
        sortOrder,
        status: gift.status || "active",
    };
}

function buildGiftTypePayload(form) {
    return {
        displayName: form.displayName.trim(),
        sortOrder: Number(form.sortOrder || 0),
        status: form.enabled ? "active" : "disabled",
        tabName: form.tabName.trim(),
    };
}

function giftTypeToForm(item) {
    const normalized = normalizeGiftTypeOption(item);
    return {
        enabled: normalized.status ? normalized.status === "active" : true,
        displayName: normalized.displayName || "",
        sortOrder: normalized.sortOrder === 0 || normalized.sortOrder ? String(normalized.sortOrder) : "0",
        tabKey: normalized.tabKey || "",
        tabName: normalized.tabName || "",
    };
}

function mergeGiftTypeOptions(items = []) {
    const byCode = new Map(defaultGiftTypeOptions.map((item) => [item.tabKey, normalizeGiftTypeOption(item)]));
    items.forEach((item) => {
        const normalized = normalizeGiftTypeOption(item);
        if (normalized.tabKey) {
            byCode.set(normalized.tabKey, {
                ...byCode.get(normalized.tabKey),
                ...normalized,
            });
        }
    });
    return Array.from(byCode.values()).sort(
        (left, right) => Number(left.sortOrder || 0) - Number(right.sortOrder || 0),
    );
}

function normalizeGiftTypeOption(item = {}) {
    const tabKey = String(item.typeCode || item.tabKey || "").trim();
    const tabName = String(item.tabName || (item.typeCode ? item.tabKey : "") || "").trim();
    const displayName = String(item.displayName || item.name || "").trim();
    return {
        ...item,
        displayName,
        tabKey,
        tabName,
    };
}

function msToDatetimeLocal(value) {
    if (!value) {
        return "";
    }
    const date = new Date(Number(value));
    if (Number.isNaN(date.getTime())) {
        return "";
    }
    const local = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
    return local.toISOString().slice(0, 16);
}

function datetimeLocalToMs(value) {
    const trimmed = String(value || "").trim();
    if (!trimmed) {
        return 0;
    }
    const parsed = new Date(trimmed).getTime();
    return Number.isFinite(parsed) ? parsed : 0;
}

function normalizedResolvedUserId(user) {
    const userId = String(user?.userId || user?.user_id || "").trim();
    if (!/^[1-9]\d*$/.test(userId)) {
        throw new Error("用户不存在或用户 ID 无效");
    }
    return userId;
}

function buildGrantPayload(form, resolvedTargetUserId) {
    const targetUserId = String(resolvedTargetUserId || "").trim();
    const reason = form.reason.trim();
    if (form.subjectType === "resource") {
        const resourceIds = grantResourceIds(form);
        return {
            resources: resourceIds.map((resourceId, index) => ({
                commandId: makeCommandId(`resource-grant-${resourceId}-${index}`),
                durationMs: Math.round(Number(form.durationDays || 0) * dayMillis),
                quantity: Number(form.quantity || 1),
                reason,
                resourceId: Number(resourceId),
                targetUserId,
            })),
            subjectType: "resource",
        };
    }
    if (form.subjectType === "vip") {
        return {
            subjectType: "vip",
            vip: {
                level: Number(form.vipLevel),
                reason,
                targetUserId,
            },
        };
    }
    if (form.subjectType === "vip_trial_card") {
        return {
            subjectType: "vip_trial_card",
            vipTrialCard: {
                commandId: makeCommandId("vip-trial-card-grant"),
                durationMs: Math.round(Number(form.durationDays || 0) * dayMillis),
                level: Number(form.vipLevel),
                reason,
                targetUserId,
            },
        };
    }
    return {
        group: {
            commandId: makeCommandId("resource-group-grant"),
            groupId: Number(form.groupId),
            reason,
            targetUserId,
        },
        subjectType: "resource_group",
    };
}

function grantResourceIds(form) {
    if (Array.isArray(form.resourceIds) && form.resourceIds.length) {
        return form.resourceIds;
    }
    return form.resourceId ? [form.resourceId] : [];
}

function isRevocableGrant(grant) {
    if (isVipTrialCardRecord(grant)) {
        return true;
    }
    if (isVipGrantRecord(grant)) {
        return false;
    }
    return grant?.grantSubjectType === "resource" || grant?.grantSubjectType === "resource_group";
}

function isVipGrantRecord(grant) {
    if (isVipTrialCardRecord(grant)) {
        return false;
    }
    const source = String(grant?.grantSource || "").trim();
    const commandId = String(grant?.commandId || "").trim();
    return (
        source === "admin_grant" ||
        source === "activity_grant" ||
        source === "vip_purchase" ||
        commandId.startsWith("vip_reward:")
    );
}

function isVipTrialCardRecord(grant) {
    return (
        grant?.grantSubjectType === "vip_trial_card" ||
        String(grant?.grantSource || "").trim() === "vip_trial_card_grant"
    );
}

function canSubmitGrantSubject(subjectType, abilities) {
    if (isVipGrantSubject(subjectType)) {
        return Boolean(abilities.canGrantVip);
    }
    return Boolean(abilities.canCreateGrant);
}

function isVipGrantSubject(subjectType) {
    return subjectType === "vip" || subjectType === "vip_trial_card";
}

function usesVipTrialCardGrant(program) {
    if (program?.grantMode) {
        return program.grantMode === "trial_card" && program.trialCardEnabled !== false;
    }
    return program?.programType === "tiered_privilege_v1" && program?.trialCardEnabled !== false;
}

function defaultGrantSubjectType(abilities) {
    if (abilities.canCreateGrant) {
        return "resource";
    }
    return abilities.canGrantVip ? "vip" : "resource";
}

function makeCommandId(prefix) {
    return `${prefix}-${Date.now()}`;
}

function toGroupResourceOptions(items, selectedGroup) {
    const selectedResources = (selectedGroup?.items || [])
        .map((item) => item.resource)
        .filter((resource) => resource?.resourceId);
    return uniqueResources([...items, ...selectedResources]).filter(
        (resource) => resource.resourceType !== "coin" && resource.status === "active" && resource.grantable !== false,
    );
}

function toGiftResourceOptions(items, selectedGift) {
    const selectedResource = selectedGift?.resource?.resourceId
        ? [selectedGift.resource]
        : selectedGift?.resourceId
          ? [{ name: String(selectedGift.resourceId), resourceId: selectedGift.resourceId, resourceType: "gift" }]
          : [];
    return uniqueResources([...items, ...selectedResource]).filter(
        (resource) => resource.resourceType === "gift" || resource.resourceId === selectedGift?.resourceId,
    );
}

function toShopResourceOptions(items) {
    return uniqueResources(items).filter(
        (resource) =>
            resourceShopSellableTypes.includes(resource.resourceType) &&
            resource.status === "active" &&
            resource.priceType === "coin" &&
            Number(resource.coinPrice || 0) > 0,
    );
}

function uniqueResources(resources) {
    const seen = new Set();
    return resources.filter((resource) => {
        if (!resource?.resourceId || seen.has(resource.resourceId)) {
            return false;
        }
        seen.add(resource.resourceId);
        return true;
    });
}

function formatDurationDays(durationMs) {
    const days = Number(durationMs || 0) / dayMillis;
    return Number.isInteger(days) ? String(days) : String(days.toFixed(1));
}
