import Add from "@mui/icons-material/Add";
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
import EditOutlined from "@mui/icons-material/EditOutlined";
import FileUploadOutlined from "@mui/icons-material/FileUploadOutlined";
import HelpOutlineOutlined from "@mui/icons-material/HelpOutlineOutlined";
import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import Checkbox from "@mui/material/Checkbox";
import CircularProgress from "@mui/material/CircularProgress";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import LinearProgress from "@mui/material/LinearProgress";
import MenuItem from "@mui/material/MenuItem";
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import { useEffect, useMemo, useRef, useState } from "react";
import {
    AdminFormDialog,
    AdminFormFieldGrid,
    AdminFormSection,
    AdminFormSwitchField,
} from "@/shared/ui/AdminFormDialog.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { UploadField } from "@/shared/ui/UploadField.jsx";
import { Button } from "@/shared/ui/Button.jsx";
import {
    AdminActionIconButton,
    AdminListBody,
    AdminListPage,
    AdminRowActions,
    AdminListToolbar,
} from "@/shared/ui/AdminListLayout.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import {
    avatarFrameKindLabels,
    avatarFrameKindOptions,
    badgeKindLabels,
    badgeKindOptions,
    badgeLevelTrackLabels,
    badgeLevelTrackOptions,
    badgeFormLabels,
    badgeFormOptions,
    resourceBatchUploadRoleLabels,
    resourceStatusFilters,
    resourcePriceTypeLabels,
    resourcePriceTypeOptions,
    resourceTypeFilters,
    resourceTypeLabels,
} from "@/features/resources/constants.js";
import { createResource, updateResourceMp4Layouts } from "@/features/resources/api";
import {
    parseResourceFolderFiles,
    resourceBatchUploadSize,
    resourcePlanToPayload,
    translateResourceCodes,
} from "@/features/resources/batchUpload.js";
import { useResourceListPage } from "@/features/resources/hooks/useResourcePages.js";
import { detectProfileCardLayout, profileCardLayoutFromMetadata } from "@/features/resources/profileCardLayout.js";
import {
    confirmMp4AlphaLayoutMetadata,
    createMp4AlphaLayout,
    detectMp4AlphaLayout,
    frameText,
    hasUnconfirmedMp4Layout,
    isMp4File,
    isMp4Source,
    mergeMp4AlphaLayoutMetadata,
    mp4FileFromSource,
    mp4AlphaLayoutFromMetadata,
    mp4AlphaLayoutOptions,
    mp4LayoutLabel,
    readMp4VideoInfo,
    removeMp4AlphaLayoutMetadata,
    updateMp4AlphaLayoutFrame,
    updateMp4AlphaLayoutKind,
} from "@/features/resources/mp4AlphaLayout.js";
import {
    metadataWithProfileCardLayout,
    removeProfileCardLayoutMetadata,
    resourceRequiresExplicitVisualFormat,
    roomBorderFormatFromMetadata,
    roomBorderFormatMetadataJSON,
    roomBorderFormatOptions,
    roomNameStyleFormFromMetadata,
    roomNameStyleInputError,
} from "@/features/resources/resourceMetadata.js";
import { uploadFilesBatch } from "@/shared/api/upload";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import styles from "@/features/resources/resources.module.css";

const resourceTypeOptions = resourceTypeFilters.filter(([value]) => value && value !== "emoji_pack");
const emptyBatchProgress = { label: "", running: false, value: 0 };
const roomBorderFormatLabels = {
    gif: "GIF",
    jpeg: "JPEG",
    mp4: "MP4",
    pag: "PAG",
    png: "PNG",
    svga: "SVGA",
    webp: "WebP",
};

function visualResourceFormatFromFile(file) {
    const filename = String(file?.name || "").toLowerCase();
    let extension = filename.includes(".") ? filename.split(".").pop() : "";
    if (extension === "jpg") {
        extension = "jpeg";
    }
    return roomBorderFormatOptions.includes(extension) ? extension : "";
}

const baseColumns = [
    {
        key: "resource",
        label: "资源",
        width: "minmax(260px, 1.5fr)",
        render: (resource) => <ResourceIdentity resource={resource} />,
    },
    {
        key: "type",
        label: "类型",
        width: "minmax(120px, 0.7fr)",
        render: (resource) => resourceTypeLabels[resource.resourceType] || resource.resourceType || "-",
    },
    {
        key: "price",
        label: "价格",
        width: "minmax(120px, 0.65fr)",
        render: (resource) => resourcePriceLabel(resource),
    },
    {
        key: "badgeForm",
        label: "资源属性",
        width: "minmax(120px, 0.65fr)",
        render: (resource) => resourceAttributeLabel(resource),
    },
    {
        key: "status",
        label: "状态",
        width: "minmax(92px, 0.55fr)",
    },
    {
        key: "managerGrant",
        label: "经理赠送",
        width: "minmax(112px, 0.6fr)",
    },
    {
        key: "time",
        label: "更新时间",
        width: "minmax(170px, 0.9fr)",
        render: (resource) => formatMillis(resource.updatedAtMs || resource.createdAtMs),
    },
    {
        key: "actions",
        label: "操作",
        width: "minmax(76px, 0.4fr)",
    },
];

export function ResourceListPage() {
    const page = useResourceListPage();
    const { showToast } = useToast();
    const folderInputRef = useRef(null);
    const [batchOpen, setBatchOpen] = useState(false);
    const [batchPlan, setBatchPlan] = useState({ errors: [], resources: [] });
    const [batchProgress, setBatchProgress] = useState(emptyBatchProgress);
    const [batchMp4Progress, setBatchMp4Progress] = useState(emptyBatchProgress);
    const [mp4BatchResult, setMp4BatchResult] = useState(null);
    const [selectedResourceIds, setSelectedResourceIds] = useState([]);
    const rawItems = page.data.items;
    const items = useMemo(() => rawItems || [], [rawItems]);
    const total = page.data.total || 0;
    const createDisabled = !page.abilities.canCreate || !page.abilities.canUpload;
    const selectedIdSet = useMemo(() => new Set(selectedResourceIds), [selectedResourceIds]);
    const selectedResources = useMemo(
        () => items.filter((resource) => selectedIdSet.has(resource.resourceId)),
        [items, selectedIdSet],
    );
    const selectedMp4Resources = useMemo(
        () => selectedResources.filter((resource) => mp4ResourceAnimationSource(resource)),
        [selectedResources],
    );
    const allChecked = items.length > 0 && items.every((resource) => selectedIdSet.has(resource.resourceId));
    const selectedOnPageCount = items.filter((resource) => selectedIdSet.has(resource.resourceId)).length;
    const someChecked = selectedOnPageCount > 0 && !allChecked;
    const batchMp4Running = batchMp4Progress.running;
    const deletingResources = page.loadingAction.startsWith("resource-delete-");
    const deletingBatch = page.loadingAction === "resource-delete-batch";
    const canSelectResources = page.abilities.canUpdate || page.abilities.canDelete;

    useEffect(() => {
        const itemIds = new Set(items.map((resource) => resource.resourceId));
        setSelectedResourceIds((current) => {
            const next = current.filter((resourceId) => itemIds.has(resourceId));
            return next.length === current.length ? current : next;
        });
    }, [items]);

    const toggleAllResources = (checked) => {
        const pageIds = items.map((resource) => resource.resourceId);
        setSelectedResourceIds((current) => {
            if (!checked) {
                return current.filter((resourceId) => !pageIds.includes(resourceId));
            }
            return Array.from(new Set([...current, ...pageIds]));
        });
    };

    const toggleResourceSelection = (resource, checked) => {
        setSelectedResourceIds((current) =>
            checked
                ? Array.from(new Set([...current, resource.resourceId]))
                : current.filter((resourceId) => resourceId !== resource.resourceId),
        );
    };

    const selectionColumn = {
        key: "select",
        className: styles.selectColumn,
        header: (
            <Checkbox
                checked={allChecked}
                disabled={!canSelectResources || items.length === 0 || batchMp4Running || deletingResources}
                indeterminate={someChecked}
                size="small"
                slotProps={{ input: { "aria-label": "选择当前页资源" } }}
                onChange={(event) => toggleAllResources(event.target.checked)}
            />
        ),
        resizable: false,
        width: "56px",
        render: (resource) => {
            return (
                <Checkbox
                    checked={selectedIdSet.has(resource.resourceId)}
                    disabled={!canSelectResources || batchMp4Running || deletingResources}
                    slotProps={{
                        input: {
                            "aria-label": `选择 ${resource.name || resource.resourceCode || resource.resourceId}`,
                        },
                    }}
                    size="small"
                    onChange={(event) => toggleResourceSelection(resource, event.target.checked)}
                />
            );
        },
    };
    const columns = [
        selectionColumn,
        ...baseColumns.map((column) =>
            column.key === "resource"
                ? {
                      ...column,
                      filter: createTextColumnFilter({
                          placeholder: "搜索资源名称、资源编码",
                          value: page.query,
                          onChange: page.changeQuery,
                      }),
                  }
                : column.key === "type"
                  ? {
                        ...column,
                        filter: createOptionsColumnFilter({
                            options: resourceTypeFilters,
                            placeholder: "搜索类型",
                            value: page.resourceType,
                            onChange: page.changeResourceType,
                        }),
                    }
                  : column.key === "status"
                    ? {
                          ...column,
                          filter: createOptionsColumnFilter({
                              options: resourceStatusFilters,
                              placeholder: "搜索状态",
                              value: page.status,
                              onChange: page.changeStatus,
                          }),
                          render: (resource) => <ResourceStatusSwitch page={page} resource={resource} />,
                      }
                    : column.key === "managerGrant"
                      ? {
                            ...column,
                            render: (resource) => <ManagerGrantSwitch page={page} resource={resource} />,
                        }
                      : column.key === "actions"
                        ? {
                              ...column,
                              render: (resource) => (
                                  <ResourceRowActions
                                      deleteDisabled={batchMp4Running || Boolean(mp4BatchResult)}
                                      page={page}
                                      resource={resource}
                                  />
                              ),
                          }
                        : column,
        ),
    ];

    const openBatchDialog = () => {
        setBatchPlan({ errors: [], resources: [] });
        setBatchProgress(emptyBatchProgress);
        setBatchOpen(true);
    };

    const closeBatchDialog = () => {
        if (!batchProgress.running) {
            setBatchOpen(false);
        }
    };

    const handleFolderChange = async (event) => {
        const plan = parseResourceFolderFiles(event.target.files);
        event.target.value = "";
        setBatchPlan(plan);
        setBatchProgress(emptyBatchProgress);
        if (!plan.resources.some((resource) => isMp4File(resource.animationFile))) {
            return;
        }

        setBatchProgress({ label: "解析 MP4 透明布局", running: true, value: 0 });
        const resources = [];
        for (let index = 0; index < plan.resources.length; index += 1) {
            const resource = plan.resources[index];
            resources.push(await resourceWithDetectedMp4Layout(resource));
            setBatchProgress({
                label: `解析 MP4 透明布局 ${index + 1}/${plan.resources.length}`,
                running: true,
                value: Math.round(((index + 1) / plan.resources.length) * 100),
            });
        }
        setBatchPlan({ ...plan, resources });
        setBatchProgress(emptyBatchProgress);
    };

    const changeBatchMp4Layout = (resourceIndex, alphaLayout) => {
        setBatchPlan((current) => ({
            ...current,
            resources: current.resources.map((resource, index) =>
                index === resourceIndex
                    ? {
                          ...resource,
                          metadataJson: updateMp4AlphaLayoutKind(resource.metadataJson, alphaLayout),
                      }
                    : resource,
            ),
        }));
    };

    const changeBatchMp4Frame = (resourceIndex, frameKey, value) => {
        setBatchPlan((current) => ({
            ...current,
            resources: current.resources.map((resource, index) =>
                index === resourceIndex
                    ? {
                          ...resource,
                          metadataJson: updateMp4AlphaLayoutFrame(resource.metadataJson, frameKey, value),
                      }
                    : resource,
            ),
        }));
    };

    const confirmBatchMp4Layout = (resourceIndex) => {
        setBatchPlan((current) => ({
            ...current,
            resources: current.resources.map((resource, index) =>
                index === resourceIndex
                    ? {
                          ...resource,
                          metadataJson: confirmMp4AlphaLayoutMetadata(resource.metadataJson),
                      }
                    : resource,
            ),
        }));
    };

    const recalculateSelectedMp4Layouts = async () => {
        if (!page.abilities.canUpdate || !selectedResources.length || batchMp4Running) {
            return;
        }
        const ignoredCount = selectedResources.length - selectedMp4Resources.length;
        if (!selectedMp4Resources.length) {
            showToast("所选资源里没有 MP4，已自动忽略", "warning");
            return;
        }
        const updates = [];
        const failures = [];
        const layoutCounts = { normal: 0, transparent: 0 };
        setBatchMp4Progress({ label: "批量重算 MP4 布局", running: true, value: 0 });
        for (let index = 0; index < selectedMp4Resources.length; index += 1) {
            const resource = selectedMp4Resources[index];
            try {
                setBatchMp4Progress({
                    label: `重算 MP4 布局 ${index + 1}/${selectedMp4Resources.length}`,
                    running: true,
                    value: Math.round((index / selectedMp4Resources.length) * 80),
                });
                const file = await mp4FileFromSource(mp4ResourceAnimationSource(resource));
                const layout = await detectMp4AlphaLayout(file);
                const metadataJson = confirmMp4AlphaLayoutMetadata(
                    mergeMp4AlphaLayoutMetadata(resource.metadataJson, layout),
                );
                const confirmedLayout = mp4AlphaLayoutFromMetadata(metadataJson);
                if (!confirmedLayout?.confirmed) {
                    throw new Error("MP4 布局确认失败");
                }
                if (confirmedLayout.alpha_layout === "normal") {
                    layoutCounts.normal += 1;
                } else {
                    layoutCounts.transparent += 1;
                }
                updates.push({ layout: confirmedLayout, metadataJson, resource, resourceId: resource.resourceId });
            } catch (err) {
                failures.push(
                    `${resource.name || resource.resourceCode || resource.resourceId}: ${err.message || "解析失败"}`,
                );
            }
        }
        if (failures.length) {
            setBatchMp4Progress(emptyBatchProgress);
            showToast(`MP4 批量重算失败：${failures.slice(0, 3).join("；")}`, "error");
            return;
        }
        if (!updates.length) {
            setBatchMp4Progress(emptyBatchProgress);
            return;
        }
        setBatchMp4Progress(emptyBatchProgress);
        setMp4BatchResult({ ignoredCount, layoutCounts, rows: updates });
    };

    const deleteSelectedResources = async () => {
        if (!page.abilities.canDelete || !selectedResources.length || deletingResources) {
            return;
        }
        const deletedResourceIds = await page.deleteResources(selectedResources);
        if (!deletedResourceIds.length) {
            return;
        }
        const deletedIdSet = new Set(deletedResourceIds);
        setSelectedResourceIds((current) => current.filter((resourceId) => !deletedIdSet.has(resourceId)));
    };

    const writeMp4BatchResult = async () => {
        const rows = mp4BatchResult?.rows || [];
        if (!rows.length || batchMp4Running) {
            return;
        }
        try {
            setBatchMp4Progress({ label: `保存 MP4 布局 ${rows.length} 个`, running: true, value: 90 });
            await updateResourceMp4Layouts(rows.map(({ metadataJson, resourceId }) => ({ metadataJson, resourceId })));
            showToast(`已更新 ${rows.length} 个 MP4 布局`, "success");
            setMp4BatchResult(null);
            setSelectedResourceIds([]);
            await page.reload();
        } catch (err) {
            showToast(err.message || "MP4 布局批量保存失败", "error");
        } finally {
            setBatchMp4Progress(emptyBatchProgress);
        }
    };

    const submitBatchUpload = async (event) => {
        event.preventDefault();
        if (!batchPlan.resources.length || batchProgress.running) {
            return;
        }
        if (batchPlan.resources.some(needsBatchMp4Confirmation)) {
            showToast("请先确认 MP4 透明布局", "error");
            return;
        }

        const resources = batchPlan.resources.map((item) => ({ ...item }));
        const uploadEntries = resources.flatMap((resource, resourceIndex) => [
            { file: resource.coverFile, resourceIndex, role: "cover" },
            { file: resource.animationFile, resourceIndex, role: "animation" },
        ]);
        const profileCardResources = resources.filter((resource) => resource.resourceType === "profile_card");
        const totalProgressSteps = Math.max(
            profileCardResources.length + uploadEntries.length + resources.length + resources.length,
            1,
        );
        let completedProgressSteps = 0;
        const updateBatchProgress = (label, completedSteps = completedProgressSteps) => {
            const value = Math.min(100, Math.round((completedSteps / totalProgressSteps) * 100));
            setBatchProgress({ label, running: true, value });
        };

        updateBatchProgress(profileCardResources.length ? "解析资料卡内容高度" : "准备上传素材");
        try {
            for (const resource of resources) {
                if (resource.resourceType !== "profile_card") {
                    continue;
                }
                try {
                    const layout = await detectProfileCardLayout(resource.animationFile);
                    resource.metadataJson = metadataWithProfileCardLayout(resource.metadataJson, layout);
                } catch {
                    showToast(`${resource.name} 内容高度解析失败`, "error");
                }
                completedProgressSteps += 1;
                updateBatchProgress(
                    `解析资料卡内容高度 ${completedProgressSteps}/${profileCardResources.length}`,
                    completedProgressSteps,
                );
            }

            updateBatchProgress(`上传素材 0/${uploadEntries.length}`);
            for (let index = 0; index < uploadEntries.length; index += resourceBatchUploadSize) {
                const chunk = uploadEntries.slice(index, index + resourceBatchUploadSize);
                updateBatchProgress(`上传素材 ${index + 1}-${index + chunk.length}/${uploadEntries.length}`);
                const results = await uploadFilesBatch(chunk.map((entry) => entry.file));
                results.forEach((result, resultIndex) => {
                    const entry = chunk[resultIndex];
                    resources[entry.resourceIndex][entry.role === "cover" ? "coverUrl" : "animationUrl"] = result.url;
                });
                completedProgressSteps += chunk.length;
                updateBatchProgress(
                    `上传素材 ${Math.min(index + chunk.length, uploadEntries.length)}/${uploadEntries.length}`,
                );
            }

            const translationStartSteps = completedProgressSteps;
            updateBatchProgress(`翻译资源编码 0/${resources.length}`);
            const translatedResources = await translateResourceCodes(resources, {
                onProgress: ({ completed, total }) => {
                    completedProgressSteps = translationStartSteps + completed;
                    updateBatchProgress(`翻译资源编码 ${completed}/${total}`, completedProgressSteps);
                },
            });

            for (let index = 0; index < translatedResources.length; index += 1) {
                updateBatchProgress(`创建资源 ${index + 1}/${translatedResources.length}`);
                await createResource(resourcePlanToPayload(translatedResources[index]));
                completedProgressSteps += 1;
                updateBatchProgress(`创建资源 ${index + 1}/${translatedResources.length}`, completedProgressSteps);
            }

            showToast(`批量上传完成，共创建 ${translatedResources.length} 个资源`, "success");
            setBatchOpen(false);
            setBatchPlan({ errors: [], resources: [] });
            await page.reload();
        } catch (err) {
            showToast(err.message || "批量上传失败", "error");
        } finally {
            setBatchProgress(emptyBatchProgress);
        }
    };

    return (
        <AdminListPage>
            <AdminListToolbar
                actions={
                    page.abilities.canCreate || page.abilities.canUpdate || page.abilities.canDelete ? (
                        <>
                            {page.abilities.canDelete ? (
                                <AdminActionIconButton
                                    disabled={!selectedResources.length || deletingResources || batchMp4Running}
                                    label={deletingBatch ? "批量删除中" : `批量删除资源（${selectedResources.length}）`}
                                    onClick={deleteSelectedResources}
                                >
                                    {deletingBatch ? (
                                        <CircularProgress size={16} />
                                    ) : (
                                        <DeleteOutlineOutlined fontSize="small" />
                                    )}
                                </AdminActionIconButton>
                            ) : null}
                            {page.abilities.canUpdate ? (
                                <AdminActionIconButton
                                    disabled={!selectedResources.length || batchMp4Running || deletingResources}
                                    label={
                                        batchMp4Running
                                            ? batchMp4Progress.label || "批量重算 MP4"
                                            : `批量重算 MP4（${selectedMp4Resources.length}/${selectedResources.length}）`
                                    }
                                    onClick={recalculateSelectedMp4Layouts}
                                >
                                    {batchMp4Running ? (
                                        <CircularProgress size={16} />
                                    ) : (
                                        <RefreshOutlined fontSize="small" />
                                    )}
                                </AdminActionIconButton>
                            ) : null}
                            {page.abilities.canCreate ? (
                                <>
                                    <AdminActionIconButton
                                        disabled={createDisabled}
                                        label="批量上传资源"
                                        onClick={openBatchDialog}
                                    >
                                        <FileUploadOutlined fontSize="small" />
                                    </AdminActionIconButton>
                                    <AdminActionIconButton label="添加资源" primary onClick={page.openCreateResource}>
                                        <Add fontSize="small" />
                                    </AdminActionIconButton>
                                </>
                            ) : null}
                        </>
                    ) : null
                }
            />
            <DataState error={page.error} loading={page.loading} onRetry={page.reload}>
                <AdminListBody>
                    <DataTable
                        columns={columns}
                        items={items}
                        minWidth="1120px"
                        pagination={
                            total > 0
                                ? {
                                      page: page.page,
                                      pageSize: page.data.pageSize || 50,
                                      total,
                                      onPageChange: page.setPage,
                                  }
                                : undefined
                        }
                        rowKey={(resource) => resource.resourceId}
                    />
                </AdminListBody>
            </DataState>
            <ResourceFormDialog
                disabled={page.activeAction === "edit" ? !page.abilities.canUpdate : createDisabled}
                form={page.form}
                loading={
                    page.activeAction === "edit"
                        ? page.loadingAction === "resource-edit"
                        : page.loadingAction === "resource-create"
                }
                mode={page.activeAction === "edit" ? "edit" : "create"}
                open={page.activeAction === "create" || page.activeAction === "edit"}
                setForm={page.setForm}
                uploadDisabled={!page.abilities.canUpload}
                onClose={page.closeAction}
                onSubmit={page.submitResource}
            />
            <ResourceBatchUploadDialog
                disabled={createDisabled}
                fileInputRef={folderInputRef}
                open={batchOpen}
                plan={batchPlan}
                progress={batchProgress}
                onChangeMp4Frame={changeBatchMp4Frame}
                onChangeMp4Layout={changeBatchMp4Layout}
                onClose={closeBatchDialog}
                onConfirmMp4Layout={confirmBatchMp4Layout}
                onFileChange={handleFolderChange}
                onSubmit={submitBatchUpload}
            />
            <Mp4BatchResultDialog
                loading={batchMp4Running}
                open={Boolean(mp4BatchResult)}
                result={mp4BatchResult}
                onClose={() => setMp4BatchResult(null)}
                onSubmit={writeMp4BatchResult}
            />
        </AdminListPage>
    );
}

async function resourceWithDetectedMp4Layout(resource) {
    if (!isMp4File(resource.animationFile)) {
        return resource;
    }
    try {
        const layout = await detectMp4AlphaLayout(resource.animationFile);
        return {
            ...resource,
            metadataJson: mergeMp4AlphaLayoutMetadata(resource.metadataJson, layout),
            mp4LayoutError: "",
        };
    } catch (err) {
        try {
            const info = await readMp4VideoInfo(resource.animationFile);
            const fallbackLayout = createMp4AlphaLayout({
                alphaLayout: "normal",
                videoH: info.height,
                videoW: info.width,
            });
            return {
                ...resource,
                metadataJson: mergeMp4AlphaLayoutMetadata(resource.metadataJson, fallbackLayout),
                mp4LayoutError: err.message || "MP4 透明布局解析失败",
            };
        } catch {
            return {
                ...resource,
                mp4LayoutError: err.message || "MP4 透明布局解析失败",
            };
        }
    }
}

function needsBatchMp4Confirmation(resource) {
    return isMp4File(resource.animationFile) && !mp4AlphaLayoutFromMetadata(resource.metadataJson)?.confirmed;
}

function ResourceBatchUploadDialog({
    disabled,
    fileInputRef,
    onChangeMp4Frame,
    onChangeMp4Layout,
    onClose,
    onConfirmMp4Layout,
    onFileChange,
    onSubmit,
    open,
    plan,
    progress,
}) {
    const submitDisabled =
        disabled || progress.running || !plan.resources.length || plan.resources.some(needsBatchMp4Confirmation);

    return (
        <AdminFormDialog
            loading={progress.running}
            open={open}
            size="wide"
            submitDisabled={submitDisabled}
            submitLabel="开始上传"
            title="批量上传资源"
            onClose={onClose}
            onSubmit={onSubmit}
        >
            <AdminFormSection
                actions={
                    <div className={styles.batchActions}>
                        <Tooltip arrow placement="top" title={<ResourceBatchRules />}>
                            <span className={styles.batchHelp} aria-label="文件规则" role="button" tabIndex={0}>
                                <HelpOutlineOutlined fontSize="small" />
                            </span>
                        </Tooltip>
                        <button
                            className={styles.batchPickButton}
                            disabled={disabled || progress.running}
                            type="button"
                            onClick={() => fileInputRef.current?.click()}
                        >
                            <FileUploadOutlined fontSize="small" />
                            选择文件夹
                        </button>
                    </div>
                }
                title="文件夹资源"
            >
                <input
                    ref={fileInputRef}
                    className={styles.batchInput}
                    directory=""
                    multiple
                    type="file"
                    webkitdirectory=""
                    onChange={onFileChange}
                />
                {progress.running ? (
                    <div className={styles.batchProgress}>
                        <div className={styles.batchProgressHeader}>
                            <span className={styles.batchProgressLabel}>
                                <CircularProgress color="inherit" size={16} />
                                {progress.label}
                            </span>
                            <span className={styles.batchProgressPercent}>{Math.round(progress.value || 0)}%</span>
                        </div>
                        <LinearProgress
                            aria-label="批量上传进度"
                            className={styles.batchProgressBar}
                            value={progress.value || 0}
                            variant="determinate"
                        />
                    </div>
                ) : null}
                <ResourceBatchPreview
                    mp4Parsing={progress.running && String(progress.label || "").includes("解析 MP4")}
                    resources={plan.resources}
                    onChangeMp4Frame={onChangeMp4Frame}
                    onChangeMp4Layout={onChangeMp4Layout}
                    onConfirmMp4Layout={onConfirmMp4Layout}
                />
            </AdminFormSection>
        </AdminFormDialog>
    );
}

function ResourceBatchRules() {
    return (
        <div className={styles.batchRules}>
            <div>只读取符合命名规则且同时存在 cover/animation 的资源，其他文件会被忽略。</div>
            <div>头像框_名称_价格_天数_cover / animation</div>
            <div>坐骑_名称_价格_天数_cover / animation</div>
            <div>礼物_名称_价格_cover / animation</div>
            <div>气泡_名称_cover / animation</div>
            <div>勋章_名称_长_cover / animation</div>
            <div>飘窗_名称_cover / animation</div>
            <div>mic声波_名称_cover / animation</div>
            <div>背景卡_名称_cover / animation</div>
            <div>天数字段会忽略；勋章不写“长”时按短徽章处理。</div>
            <div>服务端单批最多上传 10 个素材，前端会自动分批串行上传。</div>
        </div>
    );
}

function ResourceBatchPreview({ mp4Parsing, onChangeMp4Frame, onChangeMp4Layout, onConfirmMp4Layout, resources }) {
    if (!resources.length) {
        return <div className={styles.batchEmpty}>尚未选择资源文件夹</div>;
    }

    return (
        <div className={styles.batchTable}>
            <div className={styles.batchTableHead}>
                <span>资源</span>
                <span>类型</span>
                <span>价格</span>
                <span>素材</span>
                <span>MP4 布局</span>
            </div>
            {resources.map((resource, index) => (
                <div className={styles.batchTableRow} key={`${resource.resourceCode}_${index}`}>
                    <span>{resource.name}</span>
                    <span>{resourceTypeLabels[resource.resourceType] || resource.resourceType}</span>
                    <span>{resource.price > 0 ? `金币 ${resource.price}` : "免费"}</span>
                    <span>
                        {resourceBatchUploadRoleLabels.cover} / {resourceBatchUploadRoleLabels.animation}
                    </span>
                    <BatchMp4LayoutCell
                        mp4Parsing={mp4Parsing}
                        resource={resource}
                        onChangeFrame={(frameKey, value) => onChangeMp4Frame(index, frameKey, value)}
                        onChangeLayout={(alphaLayout) => onChangeMp4Layout(index, alphaLayout)}
                        onConfirm={() => onConfirmMp4Layout(index)}
                    />
                </div>
            ))}
        </div>
    );
}

function BatchMp4LayoutCell({ mp4Parsing, onChangeFrame, onChangeLayout, onConfirm, resource }) {
    if (!isMp4File(resource.animationFile)) {
        return <span className={styles.meta}>-</span>;
    }
    const layout = mp4AlphaLayoutFromMetadata(resource.metadataJson);
    if (!layout) {
        return <span className={styles.meta}>{mp4Parsing ? "解析中" : resource.mp4LayoutError || "解析失败"}</span>;
    }
    return (
        <div className={styles.mp4BatchCell}>
            <select
                className={styles.mp4Select}
                value={layout.alpha_layout}
                onChange={(event) => onChangeLayout(event.target.value)}
            >
                {mp4AlphaLayoutOptions.map(([value, label]) => (
                    <option key={value} value={value}>
                        {label}
                    </option>
                ))}
            </select>
            <span className={styles.meta}>
                {layout.video_w}x{layout.video_h} · {layout.confirmed ? "已确认" : "待确认"}
            </span>
            {layout.alpha_layout === "custom" ? (
                <div className={styles.mp4FrameGrid}>
                    <input
                        aria-label="批量 RGB 区域"
                        className={styles.mp4FrameInput}
                        value={frameText(layout.rgb_frame)}
                        onChange={(event) => onChangeFrame("rgb_frame", event.target.value)}
                    />
                    <input
                        aria-label="批量 Alpha 区域"
                        className={styles.mp4FrameInput}
                        value={frameText(layout.alpha_frame)}
                        onChange={(event) => onChangeFrame("alpha_frame", event.target.value)}
                    />
                </div>
            ) : null}
            <button className={styles.mp4ConfirmButton} disabled={layout.confirmed} type="button" onClick={onConfirm}>
                确认
            </button>
        </div>
    );
}

function Mp4BatchResultDialog({ loading, onClose, onSubmit, open, result }) {
    const rows = result?.rows || [];
    const layoutCounts = result?.layoutCounts || {};

    return (
        <Dialog fullWidth maxWidth="lg" open={open} onClose={loading ? undefined : onClose}>
            <DialogTitle>写入 MP4 布局</DialogTitle>
            <DialogContent>
                <div className={styles.mp4ResultSummary}>
                    检测完成：普通 MP4 {layoutCounts.normal || 0} 个，透明 MP4 {layoutCounts.transparent || 0} 个，非
                    MP4 自动忽略 {result?.ignoredCount || 0} 个。
                </div>
                <div className={styles.mp4ResultTableWrap}>
                    <table className={styles.mp4ResultTable}>
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>资源</th>
                                <th>类型</th>
                                <th>布局</th>
                                <th>视频宽高</th>
                                <th>RGB 区域</th>
                                <th>Alpha 区域</th>
                                <th>置信度</th>
                            </tr>
                        </thead>
                        <tbody>
                            {rows.map(({ layout, resource, resourceId }) => (
                                <tr key={resourceId}>
                                    <td>{resourceId}</td>
                                    <td>
                                        <span className={styles.mp4ResultResource}>
                                            {resource?.name || resource?.resourceCode || resourceId}
                                        </span>
                                        <span className={styles.mp4ResultCode}>{resource?.resourceCode || "-"}</span>
                                    </td>
                                    <td>
                                        {resourceTypeLabels[resource?.resourceType] || resource?.resourceType || "-"}
                                    </td>
                                    <td>{mp4LayoutLabel(layout)}</td>
                                    <td>
                                        {layout?.video_w || "-"}x{layout?.video_h || "-"}
                                    </td>
                                    <td>{frameText(layout?.rgb_frame) || "-"}</td>
                                    <td>{frameText(layout?.alpha_frame) || "-"}</td>
                                    <td>{formatConfidence(layout?.confidence)}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </div>
            </DialogContent>
            <DialogActions>
                <Button disabled={loading} onClick={onClose}>
                    取消
                </Button>
                <Button disabled={loading || !rows.length} variant="primary" onClick={onSubmit}>
                    {loading ? "写入中" : "写入"}
                </Button>
            </DialogActions>
        </Dialog>
    );
}

function ResourceRowActions({ deleteDisabled = false, page, resource }) {
    const { showToast } = useToast();
    const [recalculating, setRecalculating] = useState(false);
    const mp4AnimationSource = mp4ResourceAnimationSource(resource);
    const loading = recalculating || page.loadingAction === `resource-mp4-layout-${resource.resourceId}`;
    const deleting = page.loadingAction === `resource-delete-${resource.resourceId}`;
    const deletingBatch = page.loadingAction === "resource-delete-batch";
    const deletingResources = page.loadingAction.startsWith("resource-delete-");

    const recalculateMp4Layout = async () => {
        if (!mp4AnimationSource || !page.abilities.canUpdate || loading) {
            return;
        }
        setRecalculating(true);
        try {
            const file = await mp4FileFromSource(mp4AnimationSource);
            const layout = await detectMp4AlphaLayout(file);
            const metadataJson = mergeMp4AlphaLayoutMetadata(resource.metadataJson, layout);
            page.openEditResource({ ...resource, metadataJson });
            showToast("MP4 布局已重新计算，请确认后保存", "success");
        } catch (err) {
            showToast(err.message || "MP4 布局重新计算失败", "error");
        } finally {
            setRecalculating(false);
        }
    };

    return (
        <AdminRowActions>
            {mp4AnimationSource ? (
                <AdminActionIconButton
                    disabled={!page.abilities.canUpdate || loading}
                    label="重新计算 MP4 布局"
                    onClick={recalculateMp4Layout}
                >
                    {loading ? <CircularProgress size={16} /> : <RefreshOutlined fontSize="small" />}
                </AdminActionIconButton>
            ) : null}
            <AdminActionIconButton
                disabled={!page.abilities.canUpdate}
                label="编辑资源"
                onClick={() => page.openEditResource(resource)}
            >
                <EditOutlined fontSize="small" />
            </AdminActionIconButton>
            {page.abilities.canDelete ? (
                <AdminActionIconButton
                    disabled={deleteDisabled || deletingResources || deletingBatch}
                    label={deleting ? "删除中" : "删除资源"}
                    onClick={() => page.deleteResources([resource])}
                >
                    {deleting ? <CircularProgress size={16} /> : <DeleteOutlineOutlined fontSize="small" />}
                </AdminActionIconButton>
            ) : null}
        </AdminRowActions>
    );
}

function formatConfidence(value) {
    const confidence = Number(value || 0);
    if (!Number.isFinite(confidence)) {
        return "0";
    }
    return confidence.toFixed(3);
}

function mp4ResourceAnimationSource(resource) {
    const source = String(resource?.animationUrl || resource?.assetUrl || "").trim();
    return isMp4Source(source) ? source : "";
}

function ResourceFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, setForm, uploadDisabled }) {
    const [animationUploading, setAnimationUploading] = useState(false);
    const [previewUploading, setPreviewUploading] = useState(false);
    const submitDisabled =
        disabled || loading || animationUploading || previewUploading || hasUnconfirmedMp4Layout(form.metadataJson);
    const profileCardLayout =
        form.resourceType === "profile_card" ? profileCardLayoutFromMetadata(form.metadataJson) : null;
    const mp4Layout = mp4AlphaLayoutFromMetadata(form.metadataJson);
    const handleAnimationSelected = async (file) => {
        let detectedMp4Layout = null;
        if (isMp4File(file)) {
            try {
                detectedMp4Layout = await detectMp4AlphaLayout(file);
            } catch {
                // 布局解析只负责补充客户端渲染元数据，失败不能把 MP4 变成不可上传格式。
                detectedMp4Layout = null;
            }
        }
        let detectedProfileCardLayout = null;
        if (form.resourceType === "profile_card") {
            try {
                detectedProfileCardLayout = await detectProfileCardLayout(file);
            } catch {
                // 部分 PAG 版本无法被浏览器侧 libpag 解码，但原文件仍可供客户端消费，继续走通用上传。
                detectedProfileCardLayout = null;
            }
        }
        // 只返回当次素材的检测增量，上传成功后再合并到最新表单，避免异步旧快照覆盖用户新修改。
        return {
            mp4Layout: detectedMp4Layout,
            profileCardLayout: detectedProfileCardLayout,
            roomBorderFormat: visualResourceFormatFromFile(file),
        };
    };
    const handleAnimationChange = (animationUrl, selectionResult) => {
        setForm((previous) => {
            let metadataJson = previous.metadataJson;
            if (!animationUrl) {
                // 清除素材时，两类从该素材派生的布局元数据都必须同步清理。
                metadataJson = removeProfileCardLayoutMetadata(removeMp4AlphaLayoutMetadata(metadataJson));
                if (resourceRequiresExplicitVisualFormat(previous.resourceType)) {
                    metadataJson = roomBorderFormatMetadataJSON(metadataJson, "");
                }
            } else if (selectionResult) {
                metadataJson = selectionResult.mp4Layout
                    ? mergeMp4AlphaLayoutMetadata(metadataJson, selectionResult.mp4Layout)
                    : removeMp4AlphaLayoutMetadata(metadataJson);
                metadataJson =
                    previous.resourceType === "profile_card" && selectionResult.profileCardLayout
                        ? metadataWithProfileCardLayout(metadataJson, selectionResult.profileCardLayout)
                        : removeProfileCardLayoutMetadata(metadataJson);
                if (resourceRequiresExplicitVisualFormat(previous.resourceType)) {
                    // 换素材时格式必须跟本次文件一起更新；未知扩展名清空并要求运营显式选择，
                    // 不能沿用上一份素材的格式把 PAG 误报成 SVGA。
                    metadataJson = roomBorderFormatMetadataJSON(metadataJson, selectionResult.roomBorderFormat);
                }
            }
            return {
                ...previous,
                animationUrl,
                metadataJson,
                roomBorderFormat: resourceRequiresExplicitVisualFormat(previous.resourceType)
                    ? selectionResult
                        ? selectionResult.roomBorderFormat
                        : animationUrl
                          ? previous.roomBorderFormat
                          : ""
                    : previous.roomBorderFormat,
            };
        });
    };
    const handleSubmit = (event) => {
        if (submitDisabled) {
            // disabled 按钮之外再拦截表单事件，避免上传期间按 Enter 仍提交旧素材 URL。
            event.preventDefault();
            return;
        }
        onSubmit(event);
    };
    return (
        <AdminFormDialog
            loading={loading}
            open={open}
            submitDisabled={submitDisabled}
            title={mode === "edit" ? "编辑资源" : "添加资源"}
            onClose={onClose}
            onSubmit={handleSubmit}
        >
            <AdminFormSection title="基础信息">
                <AdminFormFieldGrid>
                    <TextField
                        disabled={disabled}
                        label="资源名称"
                        required
                        value={form.name}
                        onChange={(event) => setForm({ ...form, name: event.target.value })}
                    />
                    <TextField
                        disabled={disabled}
                        label="资源编码"
                        required
                        value={form.resourceCode}
                        onChange={(event) => setForm({ ...form, resourceCode: event.target.value })}
                    />
                    <TextField
                        disabled={disabled || animationUploading}
                        label="资源类型"
                        required
                        select
                        value={form.resourceType}
                        onChange={(event) => {
                            const resourceType = event.target.value;
                            setForm((previous) => {
                                const roomNameStyleFields =
                                    resourceType === "room_name_style"
                                        ? roomNameStyleFormFromMetadata(previous.metadataJson)
                                        : {
                                              roomNameAngleDegrees: "",
                                              roomNameStops: "",
                                              roomNameTextColors: "",
                                          };
                                const roomBorderFormat = resourceRequiresExplicitVisualFormat(resourceType)
                                    ? roomBorderFormatFromMetadata(previous.metadataJson)
                                    : "";
                                return {
                                    ...previous,
                                    avatarFrameKind:
                                        resourceType === "avatar_frame"
                                            ? previous.avatarFrameKind || "normal"
                                            : "normal",
                                    badgeForm:
                                        resourceType === "badge" ? previous.badgeForm || "tile" : previous.badgeForm,
                                    badgeKind: resourceType === "badge" ? previous.badgeKind || "normal" : "normal",
                                    levelTrack:
                                        resourceType === "badge" ||
                                        (resourceType === "avatar_frame" && previous.avatarFrameKind === "level")
                                            ? previous.levelTrack || ""
                                            : "",
                                    metadataJson:
                                        resourceType === "profile_card"
                                            ? previous.metadataJson
                                            : removeProfileCardLayoutMetadata(previous.metadataJson),
                                    ...roomNameStyleFields,
                                    resourceType,
                                    roomBorderFormat,
                                    walletAssetAmount: "",
                                };
                            });
                        }}
                    >
                        {resourceTypeOptions.map(([value, label]) => (
                            <MenuItem key={value} value={value}>
                                {label}
                            </MenuItem>
                        ))}
                    </TextField>
                    {form.resourceType === "coin" ? (
                        <TextField
                            disabled={disabled}
                            label="金币数量"
                            required
                            type="number"
                            value={form.walletAssetAmount}
                            onChange={(event) => setForm({ ...form, walletAssetAmount: event.target.value })}
                        />
                    ) : null}
                    {form.resourceType === "badge" ? (
                        <>
                            <TextField
                                disabled={disabled}
                                label="徽章属性"
                                required
                                select
                                value={form.badgeForm}
                                onChange={(event) => setForm({ ...form, badgeForm: event.target.value })}
                            >
                                {badgeFormOptions.map(([value, label]) => (
                                    <MenuItem key={value} value={value}>
                                        {label}
                                    </MenuItem>
                                ))}
                            </TextField>
                            <TextField
                                disabled={disabled}
                                label="徽章类型"
                                required
                                select
                                value={form.badgeKind}
                                onChange={(event) =>
                                    setForm({
                                        ...form,
                                        badgeKind: event.target.value,
                                        levelTrack: event.target.value === "level" ? form.levelTrack : "",
                                    })
                                }
                            >
                                {badgeKindOptions.map(([value, label]) => (
                                    <MenuItem key={value} value={value}>
                                        {label}
                                    </MenuItem>
                                ))}
                            </TextField>
                            {form.badgeKind === "level" ? (
                                <TextField
                                    disabled={disabled}
                                    label="所属等级"
                                    required
                                    select
                                    value={form.levelTrack}
                                    onChange={(event) => setForm({ ...form, levelTrack: event.target.value })}
                                >
                                    {badgeLevelTrackOptions.map(([value, label]) => (
                                        <MenuItem key={value} value={value}>
                                            {label}
                                        </MenuItem>
                                    ))}
                                </TextField>
                            ) : null}
                        </>
                    ) : null}
                    {form.resourceType === "avatar_frame" ? (
                        <>
                            <TextField
                                disabled={disabled}
                                label="头像框类型"
                                required
                                select
                                value={form.avatarFrameKind || "normal"}
                                onChange={(event) =>
                                    setForm({
                                        ...form,
                                        avatarFrameKind: event.target.value,
                                        levelTrack: event.target.value === "level" ? form.levelTrack : "",
                                    })
                                }
                            >
                                {avatarFrameKindOptions.map(([value, label]) => (
                                    <MenuItem key={value} value={value}>
                                        {label}
                                    </MenuItem>
                                ))}
                            </TextField>
                            {form.avatarFrameKind === "level" ? (
                                <TextField
                                    disabled={disabled}
                                    label="所属等级"
                                    required
                                    select
                                    value={form.levelTrack}
                                    onChange={(event) => setForm({ ...form, levelTrack: event.target.value })}
                                >
                                    {badgeLevelTrackOptions.map(([value, label]) => (
                                        <MenuItem key={value} value={value}>
                                            {label}
                                        </MenuItem>
                                    ))}
                                </TextField>
                            ) : null}
                        </>
                    ) : null}
                </AdminFormFieldGrid>
            </AdminFormSection>
            <AdminFormSection title="价格">
                <AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
                    <TextField
                        disabled={disabled}
                        label="价格类型"
                        required
                        select
                        value={form.priceType}
                        onChange={(event) =>
                            setForm({
                                ...form,
                                coinPrice: event.target.value === "free" ? "0" : form.coinPrice,
                                priceType: event.target.value,
                            })
                        }
                    >
                        {resourcePriceTypeOptions.map(([value, label]) => (
                            <MenuItem key={value} value={value}>
                                {label}
                            </MenuItem>
                        ))}
                    </TextField>
                    <TextField
                        disabled={disabled || form.priceType === "free"}
                        label="价格"
                        required={form.priceType === "coin"}
                        type="number"
                        value={form.priceType === "free" ? "0" : form.coinPrice}
                        onChange={(event) => setForm({ ...form, coinPrice: event.target.value })}
                    />
                </AdminFormFieldGrid>
            </AdminFormSection>
            <AdminFormSection title="素材">
                {form.resourceType === "room_name_style" ? (
                    <RoomNameStyleEditor disabled={disabled} form={form} setForm={setForm} />
                ) : null}
                {resourceRequiresExplicitVisualFormat(form.resourceType) ? (
                    <AdminFormFieldGrid>
                        <TextField
                            disabled={disabled}
                            helperText="跟随资源快照返回，Flutter 不从 URL 后缀猜格式"
                            label={form.resourceType === "mic_seat_animation" ? "麦位动效素材格式" : "房间边框素材格式"}
                            required
                            select
                            value={form.roomBorderFormat}
                            onChange={(event) => {
                                const roomBorderFormat = event.target.value;
                                setForm((previous) => ({
                                    ...previous,
                                    metadataJson: roomBorderFormatMetadataJSON(previous.metadataJson, roomBorderFormat),
                                    roomBorderFormat,
                                }));
                            }}
                        >
                            {roomBorderFormatOptions.map((format) => (
                                <MenuItem key={format} value={format}>
                                    {roomBorderFormatLabels[format] || format}
                                </MenuItem>
                            ))}
                        </TextField>
                    </AdminFormFieldGrid>
                ) : null}
                <AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
                    <UploadField
                        key={open ? "resource-preview-open" : "resource-preview-closed"}
                        disabled={disabled || uploadDisabled}
                        kind="file"
                        label={form.resourceType === "room_name_style" ? "样式预览图（可选）" : "资源封面"}
                        showSourceActions
                        value={form.previewUrl}
                        onChange={(previewUrl) => setForm((previous) => ({ ...previous, previewUrl }))}
                        onUploadingChange={setPreviewUploading}
                    />
                    <UploadField
                        key={open ? "resource-animation-open" : "resource-animation-closed"}
                        disabled={disabled || uploadDisabled}
                        kind="file"
                        label={form.resourceType === "room_name_style" ? "样式动效（可选）" : "动效素材"}
                        mp4Layout={mp4Layout}
                        showSourceActions
                        value={form.animationUrl}
                        onChange={handleAnimationChange}
                        onFileSelected={handleAnimationSelected}
                        onUploadingChange={setAnimationUploading}
                    />
                </AdminFormFieldGrid>
                {mp4Layout ? (
                    <Mp4AlphaLayoutEditor
                        disabled={disabled || animationUploading}
                        metadataJson={form.metadataJson}
                        onChange={(metadataJson) => setForm((previous) => ({ ...previous, metadataJson }))}
                    />
                ) : isMp4Source(form.animationUrl) ? (
                    <div className={styles.mp4LayoutEmpty}>请重新选择 MP4 文件解析布局，或保存前补充布局元数据。</div>
                ) : null}
                {profileCardLayout ? (
                    <AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
                        <TextField disabled label="content_top" value={String(profileCardLayout.content_top)} />
                        <TextField disabled label="内容高度" value={String(profileCardLayout.content_height)} />
                    </AdminFormFieldGrid>
                ) : null}
            </AdminFormSection>
            <AdminFormSection title="状态">
                <AdminFormSwitchField
                    checked={form.enabled}
                    checkedLabel="启用"
                    disabled={disabled}
                    label="资源状态"
                    uncheckedLabel="禁用"
                    onChange={(checked) => setForm({ ...form, enabled: checked })}
                />
                <AdminFormSwitchField
                    checked={Boolean(form.managerGrantEnabled)}
                    checkedLabel="开启"
                    disabled={disabled}
                    label="经理赠送"
                    uncheckedLabel="关闭"
                    onChange={(checked) => setForm({ ...form, managerGrantEnabled: checked })}
                />
            </AdminFormSection>
        </AdminFormDialog>
    );
}

function RoomNameStyleEditor({ disabled, form, setForm }) {
    const styleError = roomNameStyleInputError(form);
    return (
        <AdminFormFieldGrid>
            <TextField
                disabled={disabled}
                error={Boolean(styleError)}
                helperText={styleError || "支持 1 至 8 个 #RRGGBB / #RRGGBBAA，使用逗号分隔"}
                label="房名渐变颜色"
                required
                value={form.roomNameTextColors}
                onChange={(event) => setForm({ ...form, roomNameTextColors: event.target.value })}
            />
            <TextField
                disabled={disabled}
                helperText="可选；数量需与颜色一致，按 0 至 1 递增，例如 0, 0.5, 1"
                label="渐变位置"
                value={form.roomNameStops}
                onChange={(event) => setForm({ ...form, roomNameStops: event.target.value })}
            />
            <TextField
                disabled={disabled}
                helperText="可选；Flutter 按服务端快照直接渲染"
                label="渐变角度"
                type="number"
                value={form.roomNameAngleDegrees}
                onChange={(event) => setForm({ ...form, roomNameAngleDegrees: event.target.value })}
            />
        </AdminFormFieldGrid>
    );
}

function Mp4AlphaLayoutEditor({ disabled, metadataJson, onChange }) {
    const layout = mp4AlphaLayoutFromMetadata(metadataJson);
    if (!layout) {
        return null;
    }
    const transparentLayout = layout.alpha_layout !== "normal";
    return (
        <div className={styles.mp4LayoutPanel}>
            <AdminFormFieldGrid columns="repeat(3, minmax(0, 1fr))">
                <TextField
                    disabled={disabled}
                    label="MP4 布局"
                    select
                    value={layout.alpha_layout}
                    onChange={(event) => onChange(updateMp4AlphaLayoutKind(metadataJson, event.target.value))}
                >
                    {mp4AlphaLayoutOptions.map(([value, label]) => (
                        <MenuItem key={value} value={value}>
                            {label}
                        </MenuItem>
                    ))}
                </TextField>
                <TextField disabled label="视频宽高" value={`${layout.video_w} x ${layout.video_h}`} />
                <TextField disabled label="确认状态" value={layout.confirmed ? "已确认" : "待确认"} />
            </AdminFormFieldGrid>
            <AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
                <TextField
                    disabled={disabled || layout.alpha_layout !== "custom"}
                    label="rgb_frame"
                    value={frameText(layout.rgb_frame)}
                    onChange={(event) =>
                        onChange(updateMp4AlphaLayoutFrame(metadataJson, "rgb_frame", event.target.value))
                    }
                />
                <TextField
                    disabled={disabled || !transparentLayout || layout.alpha_layout !== "custom"}
                    label="alpha_frame"
                    value={frameText(layout.alpha_frame)}
                    onChange={(event) =>
                        onChange(updateMp4AlphaLayoutFrame(metadataJson, "alpha_frame", event.target.value))
                    }
                />
            </AdminFormFieldGrid>
            <div className={styles.mp4LayoutActions}>
                <span className={styles.meta}>
                    {mp4LayoutLabel(layout)} · confidence {layout.confidence || 0}
                </span>
                <button
                    className={styles.mp4ConfirmButton}
                    disabled={disabled || layout.confirmed}
                    type="button"
                    onClick={() => onChange(confirmMp4AlphaLayoutMetadata(metadataJson))}
                >
                    确认 MP4 布局
                </button>
            </div>
        </div>
    );
}

function ResourceIdentity({ resource }) {
    const preview = imageURL(resource.previewUrl || resource.assetUrl);
    return (
        <div className={styles.identity}>
            <span className={styles.thumb}>
                {preview ? <img src={preview} alt="" /> : <Inventory2Outlined fontSize="small" />}
            </span>
            <div className={styles.identityText}>
                <span className={styles.name}>{resource.name || "-"}</span>
                <span className={styles.meta}>{resource.resourceCode || resource.resourceId}</span>
            </div>
        </div>
    );
}

function ResourceStatusSwitch({ page, resource }) {
    const checked = resource.status === "active";
    return (
        <AdminSwitch
            checked={checked}
            disabled={!page.abilities.canUpdate || page.loadingAction === `resource-status-${resource.resourceId}`}
            inputProps={{ "aria-label": checked ? "禁用资源" : "启用资源" }}
            onChange={(event) => page.toggleResource(resource, event.target.checked)}
        />
    );
}

function ManagerGrantSwitch({ page, resource }) {
    const checked = Boolean(resource.managerGrantEnabled);
    return (
        <AdminSwitch
            checked={checked}
            checkedLabel="开启"
            disabled={
                !page.abilities.canUpdate || page.loadingAction === `resource-manager-grant-${resource.resourceId}`
            }
            inputProps={{ "aria-label": checked ? "关闭经理赠送" : "开启经理赠送" }}
            uncheckedLabel="关闭"
            onChange={(event) => page.toggleManagerGrant(resource, event.target.checked)}
        />
    );
}

function resourcePriceLabel(resource) {
    if (resource.priceType === "free") {
        return resourcePriceTypeLabels.free;
    }
    if (resource.priceType === "coin") {
        return `金币 ${formatNumber(resource.coinPrice)}`;
    }
    return "未配置";
}

function resourceAttributeLabel(resource) {
    if (resource.resourceType === "badge") {
        return badgeFormLabel(resource);
    }
    if (resource.resourceType === "avatar_frame") {
        return avatarFrameKindLabel(resource);
    }
    return "-";
}

function badgeFormLabel(resource) {
    if (resource.resourceType !== "badge") {
        return "-";
    }
    const formLabel = badgeFormLabels[resource.badgeForm || badgeFormFromMetadata(resource.metadataJson)] || "未配置";
    const kind = resource.badgeKind || badgeKindFromMetadata(resource.metadataJson) || "normal";
    const kindLabel = badgeKindLabels[kind] || badgeKindLabels.normal;
    const track = resource.levelTrack || levelTrackFromMetadata(resource.metadataJson);
    const trackLabel = kind === "level" && track ? badgeLevelTrackLabels[track] : "";
    return [formLabel, kindLabel, trackLabel].filter(Boolean).join(" · ");
}

function avatarFrameKindLabel(resource) {
    const kind = resource.avatarFrameKind || avatarFrameKindFromMetadata(resource.metadataJson) || "normal";
    const kindLabel = avatarFrameKindLabels[kind] || avatarFrameKindLabels.normal;
    const track = resource.levelTrack || levelTrackFromMetadata(resource.metadataJson);
    const trackLabel = kind === "level" && track ? badgeLevelTrackLabels[track] : "";
    return [kindLabel, trackLabel].filter(Boolean).join(" · ");
}

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

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

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

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

function imageURL(value) {
    const url = String(value || "").trim();
    if (!url) {
        return "";
    }
    return /\.(avif|gif|jpe?g|png|webp)(\?|#|$)/i.test(url) ? url : "";
}

function formatNumber(value) {
    return Number(value || 0).toLocaleString("zh-CN");
}
