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 {
    createEmojiPack,
    createGift,
    createResource,
    createResourceGroup,
    deleteGift as requestDeleteGift,
    disableResourceShopItem,
    disableGift,
    disableResource,
    disableResourceGroup,
    enableResourceShopItem,
    enableGift,
    enableResource,
    enableResourceGroup,
    grantResource,
    grantResourceGroup,
    listEmojiPackCategories,
    listEmojiPacks,
    listGiftTypes,
    listGifts,
    listResourceGrants,
    listResourceGroups,
    listResourceShopItems,
    listResourceShopPurchaseOrders,
    listResources,
    lookupResourceGrantTarget,
    updateGift,
    updateGiftTypes,
    updateResource,
    updateResourceGroup,
    upsertResourceShopItems,
} from "@/features/resources/api";
import {
    cpRelationTypeLabels,
    cpRelationTypeOptions,
    defaultGiftTypeOptions,
    resourceShopSellableTypes,
} from "@/features/resources/constants.js";
import { useResourceAbilities } from "@/features/resources/permissions.js";
import { resourceMetadataJSON } from "@/features/resources/resourceMetadata.js";
import {
    emojiPackFormSchema,
    giftFormSchema,
    giftTypeConfigFormSchema,
    resourceFormSchema,
    resourceGrantFormSchema,
    resourceGroupCreateFormSchema,
    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 || "",
    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.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",
    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 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) : "",
        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 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) {
    const items = [];
    let total = Number.POSITIVE_INFINITY;

    // 后台会把选项接口的 page_size 截断到 100；抽屉候选项必须按真实分页拉全，避免只展示第一页资源。
    for (let page = 1; page <= optionMaxPages; 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("资源选项分页过多，请收窄筛选条件");
}

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: "",
});

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 { 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 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 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 () => {
                if (nextStatus === "active") {
                    await enableResource(resource.resourceId);
                } else {
                    await disableResource(resource.resourceId);
                }
                await result.reload();
            },
        );
    };

    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,
                });
                await updateResource(resource.resourceId, buildResourcePayload(formValue));
                await result.reload();
            },
        );
    };

    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({ page, query, result, setPage, setQuery }),
        abilities,
        activeAction,
        changeResourceType: resetSetter(setResourceType, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        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 [purchaseUserKeyword, setPurchaseUserKeyword] = useState("");
    const [purchaseResourceType, setPurchaseResourceType] = useState("");
    const [purchaseKeyword, setPurchaseKeyword] = useState("");
    const [drawerQuery, setDrawerQuery] = useState("");
    const [drawerResourceType, setDrawerResourceType] = useState("");
    const [batchDurationDays, setBatchDurationDays] = useState("1");
    const [resourceOptions, setResourceOptions] = useState([]);
    const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
    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,
            user_keyword: purchaseUserKeyword,
        }),
        [purchaseKeyword, purchaseResourceType, purchaseUserKeyword],
    );
    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(() => {
        const items = new Map();
        (result.data?.items || []).forEach((item) => {
            if (item.resourceId) {
                items.set(Number(item.resourceId), item);
            }
        });
        return items;
    }, [result.data?.items]);
    const selectedDrafts = useMemo(
        () =>
            Object.values(draftByResourceId)
                .map((draft) => ({
                    ...draft,
                    resource: resourceOptions.find(
                        (resource) => String(resource.resourceId) === String(draft.resourceId),
                    ),
                }))
                .sort((left, right) => Number(left.sortOrder || 0) - Number(right.sortOrder || 0)),
        [draftByResourceId, resourceOptions],
    );
    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);
        fetchAllOptionPages(listResources, { status: "active" })
            .then((data) => {
                if (!ignore) {
                    setResourceOptions(toShopResourceOptions(data.items || []));
                }
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载售卖资源失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setResourceOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [drawerOpen, showToast]);

    const openShopDrawer = () => {
        setDraftByResourceId({});
        setDrawerQuery("");
        setDrawerResourceType("");
        setBatchDurationDays("1");
        setDrawerOpen(true);
    };

    const closeShopDrawer = () => {
        setDrawerOpen(false);
        setDraftByResourceId({});
    };

    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]) {
                const next = { ...current };
                delete next[resourceId];
                return next;
            }
            const existing = shopItemsByResourceId.get(Number(resource.resourceId));
            return {
                ...current,
                [resourceId]: {
                    durationDays: String(existing?.durationDays || batchDurationDays),
                    effectiveFrom: msToDatetimeLocal(existing?.effectiveFromMs),
                    effectiveTo: msToDatetimeLocal(existing?.effectiveToMs),
                    resourceId,
                    sortOrder: String(existing?.sortOrder ?? Object.keys(current).length),
                },
            };
        });
    };

    const updateDraftItem = (resourceId, patch) => {
        setDraftByResourceId((current) => ({
            ...current,
            [resourceId]: {
                ...current[resourceId],
                ...patch,
            },
        }));
    };

    const applyBatchDuration = (durationDays) => {
        setBatchDurationDays(durationDays);
        setDraftByResourceId((current) =>
            Object.fromEntries(
                Object.entries(current).map(([resourceId, draft]) => [resourceId, { ...draft, durationDays }]),
            ),
        );
    };

    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) => ({
                    durationDays: Number(item.durationDays),
                    effectiveFromMs: datetimeLocalToMs(item.effectiveFrom),
                    effectiveToMs: datetimeLocalToMs(item.effectiveTo),
                    resourceId: Number(item.resourceId),
                    shopItemId: shopItemsByResourceId.get(Number(item.resourceId))?.shopItemId,
                    sortOrder: Number(item.sortOrder || 0),
                    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,
        applyBatchDuration,
        batchDurationDays,
        changeDrawerQuery: setDrawerQuery,
        changeDrawerResourceType: setDrawerResourceType,
        changeResourceType: resetSetter(setResourceType, setPage),
        changeStatus: resetSetter(setStatus, setPage),
        closeShopDrawer,
        closePurchaseDrawer,
        drawerOpen,
        drawerQuery,
        drawerResourceType,
        filteredResourceOptions,
        loadingAction,
        openShopDrawer,
        openPurchaseDrawer,
        purchaseData: purchaseResult.data,
        purchaseDrawerOpen,
        purchaseError: purchaseResult.error,
        purchaseKeyword,
        purchaseLoading: purchaseResult.loading,
        purchasePage,
        purchaseResourceType,
        purchaseUserKeyword,
        reloadPurchaseOrders: purchaseResult.reload,
        setPurchasePage,
        resourceOptionsLoading,
        resourceType,
        resetFilters,
        selectedDrafts,
        selectedResourceIds: new Set(Object.keys(draftByResourceId)),
        status,
        submitShopItems,
        toggleDraftResource,
        toggleShopItem,
        changePurchaseKeyword: resetSetter(setPurchaseKeyword, setPurchasePage),
        changePurchaseResourceType: resetSetter(setPurchaseResourceType, setPurchasePage),
        changePurchaseUserKeyword: resetSetter(setPurchaseUserKeyword, setPurchasePage),
        updateDraftItem,
    };
}

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 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]);

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

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

    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 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,
        loadingAction,
        openCreateGroup,
        openEditGroup,
        removeGroupItem,
        resourceOptions,
        resourceOptionsLoading,
        selectedGroup,
        resetFilters,
        setForm,
        status,
        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 [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);
        fetchAllOptionPages(listResources, { resource_type: "gift" })
            .then((data) => {
                if (!ignore) {
                    setResourceOptions(toGiftResourceOptions(data.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 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");
        try {
            const payload = buildGiftPayload(parseForm(giftFormSchema, form));
            if (editing) {
                await updateGift(selectedGift.giftId, payload);
            } else {
                await createGift(payload);
            }
            showToast(editing ? "礼物已更新" : "礼物已创建", "success");
            closeAction();
            await result.reload();
        } catch (err) {
            showToast(err.message || "操作失败", "error");
        } finally {
            setLoadingAction("");
        }
    };

    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,
        loadingAction,
        loadingRegions,
        openCreateGift,
        openEditGift,
        openGiftTypeDialog,
        regionId,
        regionOptions,
        resourceOptions,
        resourceOptionsLoading,
        selectedGift,
        resetFilters,
        selectGiftResource,
        setForm,
        saveGiftSortOrder,
        submitGiftTypes,
        status,
        submitGift,
        toggleGift,
        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 { showToast } = useToast();
    const [query, setQuery] = useState("");
    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 [optionsLoading, setOptionsLoading] = useState(false);
    const [loadingAction, setLoadingAction] = useState("");
    const filters = useMemo(
        () => ({
            status,
            target_user_id: query.trim(),
        }),
        [query, status],
    );
    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);
        Promise.all([
            fetchAllOptionPages(listResources, { status: "active" }),
            fetchAllOptionPages(listGifts, { status: "active" }),
            listGiftTypes(),
            fetchAllOptionPages(listResourceGroups, { status: "active" }),
        ])
            .then(([resources, gifts, giftTypes, groups]) => {
                if (!ignore) {
                    setResourceOptions(
                        (resources.items || []).filter(
                            (resource) => resource.status !== "disabled" && resource.grantable !== false,
                        ),
                    );
                    setGiftOptions((gifts.items || []).filter((gift) => gift.resourceId && gift.status !== "disabled"));
                    setGiftTypeOptions(mergeGiftTypeOptions(giftTypes));
                    setGroupOptions(groups.items || []);
                }
            })
            .catch((err) => {
                if (!ignore) {
                    showToast(err.message || "加载资源选项失败", "error");
                }
            })
            .finally(() => {
                if (!ignore) {
                    setOptionsLoading(false);
                }
            });
        return () => {
            ignore = true;
        };
    }, [activeAction, showToast]);

    const openCreateGrant = () => {
        setForm(emptyGrantForm());
        setActiveAction("create");
    };

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

    const submitGrant = async (event) => {
        event.preventDefault();
        if (!abilities.canCreateGrant) {
            return;
        }
        setLoadingAction("grant-create");
        try {
            const parsedForm = parseForm(resourceGrantFormSchema, form);
            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 {
                await grantResourceGroup(payload.group);
            }
            showToast("资源已赠送", "success");
            closeAction();
            setForm(emptyGrantForm());
            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,
        changeStatus: resetSetter(setStatus, setPage),
        closeAction,
        form,
        giftOptions,
        giftTypeOptions,
        groupOptions,
        loadingAction,
        openCreateGrant,
        optionsLoading,
        resourceOptions,
        resetFilters,
        setForm,
        status,
        submitGrant,
    };
}

function sharedPageState({ page, query, result, setPage, setQuery }) {
    return {
        data: result.data || emptyData,
        error: result.error,
        loading: result.loading,
        page,
        query,
        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 animationUrl = form.animationUrl.trim();
    return {
        amount: isCoin ? Number(form.walletAssetAmount) : 0,
        animationUrl,
        assetUrl: animationUrl,
        badgeForm: resourceType === "badge" ? form.badgeForm : undefined,
        badgeKind: resourceType === "badge" ? form.badgeKind || "normal" : undefined,
        coinPrice: form.priceType === "coin" ? Number(form.coinPrice) : 0,
        levelTrack: resourceType === "badge" && form.badgeKind === "level" ? form.levelTrack : undefined,
        managerGrantEnabled: Boolean(form.managerGrantEnabled),
        metadataJson: resourceMetadataJSON(resourceType, form.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 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 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",
        };
    }
    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 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));
}
