import { type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node";
import { useLoaderData, useNavigate, useSearchParams } from "@remix-run/react";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import axios from "axios";

import indexStyles from "~/styles/pages/rule-groups_index.css?url";
import {
  getDocumentTypes,
  getEntryModules,
  getRuleSets,
  type DocumentType,
  type EntryModuleOption,
  type RuleSetOption,
} from "~/api/document-types/document-types";
import { Button } from "~/components/ui/Button";
import { Card } from "~/components/ui/Card";
import { API_BASE_URL } from "~/config/api-config";
import { usePermission } from "~/hooks/usePermission";
import { parseRuleSummariesFromYaml, type RuleSummary } from "~/utils/rule-yaml-parser";

export function links() {
  return [{ rel: "stylesheet", href: indexStyles }];
}

export const meta: MetaFunction = () => {
  return [
    { title: "评查点分组管理 - 中国烟草AI合同及卷宗审核系统" },
    { name: "description", content: "维护业务大类、具体业务类型与规则集之间的运行绑定关系。" },
  ];
};

interface BindingItem {
  id: number;
  group_id: number;
  rule_set_id: number;
  rule_type_binding_id?: number | null;
  priority: number;
  is_active: boolean;
  note?: string | null;
  rule_type?: string | null;
  rule_name?: string | null;
  current_version_id?: number | null;
  fallback_version_id?: number | null;
  has_usable_version: boolean;
  usable_rule_count: number;
}

interface RuleGroupNode {
  id: number;
  pid: number;
  name: string;
  code: string;
  description?: string | null;
  document_type_id?: number | null;
  document_type_name?: string | null;
  entry_module_id?: number | null;
  entry_module_name?: string | null;
  sort_order: number;
  is_enabled: boolean;
  created_at?: string | null;
  updated_at?: string | null;
  rule_count?: number | null;
  bindings: BindingItem[];
  children?: RuleGroupNode[] | null;
}

interface LoaderData {
  groups: RuleGroupNode[];
  docTypes: DocumentType[];
  entryModules: EntryModuleOption[];
  ruleSets: RuleSetOption[];
  frontendJWT?: string | null;
}

interface RuleTemplatePayload {
  groupId?: number;
  groupName?: string;
  parentGroupName?: string;
  documentTypeName?: string;
  entryModuleName?: string;
  ruleType?: string;
  ruleName?: string;
  yamlTemplate?: string;
  yamlText?: string;
  ossPreviewKey?: string;
}

interface RuleDraftCreateResult {
  packId?: number | null;
  groupId?: number | null;
  groupName?: string | null;
  ruleSetId?: number | null;
  ruleSetName?: string | null;
  ruleName?: string | null;
  ruleType?: string | null;
  versionId?: number | null;
  versionNo?: string | null;
  bindingId?: number | null;
}

interface GroupFormState {
  id?: number;
  mode: "create" | "edit";
  pid: number;
  name: string;
  code: string;
  description: string;
  documentTypeId: string;
  entryModuleId: string;
  sortOrder: number;
  isEnabled: boolean;
}

interface BindingFormState {
  groupId: number;
  bindingId?: number;
  mode: "create" | "edit";
  ruleSetId: string;
  priority: number;
  note: string;
  isActive: boolean;
}

type ChildGroupStats = {
  siblingCount: number;
  siblingIndex: number;
};

type ChildGroupHealth = "ready" | "partial" | "empty";
type RootGroupHealth = "ready" | "partial";

function authHeaders(token?: string | null): Record<string, string> {
  const headers: Record<string, string> = {};
  if (token) headers.Authorization = `Bearer ${token}`;
  return headers;
}

async function fetchGroupTree(token?: string | null): Promise<RuleGroupNode[]> {
  const response = await axios.get(`${API_BASE_URL}/api/v3/evaluation-point-groups/all`, {
    headers: authHeaders(token),
    params: { include_disabled: true, with_rule_count: true },
  });
  const payload = response?.data?.data ?? response?.data ?? [];
  return Array.isArray(payload) ? payload : [];
}

export async function loader({ request }: LoaderFunctionArgs) {
  const { getUserSession } = await import("~/api/login/auth.server");
  const { frontendJWT, userInfo } = await getUserSession(request);
  const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server");

  await requireRoutePermission("/rule-groups", userInfo?.role || "", frontendJWT || undefined);

  try {
    const [groups, docTypesRes, entryModulesRes, ruleSetsRes] = await Promise.all([
      fetchGroupTree(frontendJWT),
      getDocumentTypes({ page: 1, pageSize: 500 }, frontendJWT),
      getEntryModules(frontendJWT),
      getRuleSets(frontendJWT),
    ]);

    return Response.json({
      groups,
      docTypes: docTypesRes.data?.types || [],
      entryModules: entryModulesRes.data || [],
      ruleSets: ruleSetsRes.data || [],
      frontendJWT,
    } satisfies LoaderData);
  } catch (error) {
    if (axios.isAxiosError(error) && error.response?.status === 403) {
      throw new Response("无权访问评查点分组页面", { status: 403 });
    }
    throw error;
  }
}

function formatVersionLabel(binding: BindingItem): string {
  if (binding.current_version_id) return `当前 #${binding.current_version_id}`;
  if (binding.fallback_version_id) return `回退 #${binding.fallback_version_id}`;
  return "未配置";
}

function formatDateTime(value?: string | null): string {
  if (!value) return "-";
  const normalized = value.replace("T", " ").replace(/\.\d+/, "");
  return normalized.slice(0, 19);
}

function toTopGroups(groups: RuleGroupNode[]): RuleGroupNode[] {
  return [...groups].sort((a, b) => (a.sort_order - b.sort_order) || (a.id - b.id));
}

function buildChildGroupStats(topGroups: RuleGroupNode[]): Record<number, ChildGroupStats> {
  const stats: Record<number, ChildGroupStats> = {};
  for (const root of topGroups) {
    const sorted = [...(root.children || [])].sort((a, b) => (a.sort_order - b.sort_order) || (a.id - b.id));
    sorted.forEach((item, index) => {
      stats[item.id] = { siblingCount: sorted.length, siblingIndex: index + 1 };
    });
  }
  return stats;
}

function getChildGroupHealth(group: RuleGroupNode): ChildGroupHealth {
  if (!group.bindings.length) return "empty";
  const readyCount = group.bindings.filter(
    (item) => item.is_active && item.has_usable_version && (item.usable_rule_count || 0) > 0,
  ).length;
  return readyCount === group.bindings.length ? "ready" : "partial";
}

function getChildGroupUsableRuleCount(group: RuleGroupNode): number {
  return group.bindings.reduce((sum, item) => sum + (item.usable_rule_count || 0), 0);
}

function getChildGroupReadyBindingCount(group: RuleGroupNode): number {
  return group.bindings.filter(
    (item) => item.is_active && item.has_usable_version && (item.usable_rule_count || 0) > 0,
  ).length;
}

function getSubtypeGroupDisplayName(group: RuleGroupNode): string {
  const name = (group.name || "").trim();
  const code = (group.code || "").trim();
  if (name === "通用" || code.endsWith(".default")) {
    return `默认子类型（${name || "通用"}）`;
  }
  return group.name;
}

function getRootGroupMode(group: RuleGroupNode): "category" | "legacy-doc-type-root" {
  return group.document_type_id ? "legacy-doc-type-root" : "category";
}

function getRootGroupSubtitle(group: RuleGroupNode): string {
  return getRootGroupMode(group) === "category" ? "一级分组 · 业务大类" : "一级分组 · 兼容中的具体类型";
}

function getRootGroupDescription(group: RuleGroupNode): string {
  if (getRootGroupMode(group) === "category") {
    return "用于承接某个入口模块下的业务大类，再向下拆分具体业务类型。";
  }
  return "这是历史过渡数据：当前一级仍直接挂了具体文档类型，后续应迁入某个业务大类下。";
}

function getRootGroupHealth(group: RuleGroupNode): RootGroupHealth {
  if (!group.entry_module_id) return "partial";
  const children = group.children || [];
  if (!children.length) return "partial";
  return children.every((child) => getChildGroupHealth(child) === "ready") ? "ready" : "partial";
}

function getRootGroupStatusText(group: RuleGroupNode): string {
  if (!group.entry_module_id) return "待绑定入口模块";
  if (!(group.children || []).length) return "待补充二级分组";
  return getRootGroupHealth(group) === "ready" ? "运行链路已就绪" : "存在待整理配置";
}

function getRootGroupWarningText(group: RuleGroupNode): string | null {
  if (!group.entry_module_id) {
    return "当前一级分组还未绑定入口模块，暂时不会在上传入口形成完整链路。";
  }
  if (!(group.children || []).length) {
    return "当前一级分组下还没有二级分组，无法承接具体业务类型。";
  }
  const pendingChildren = (group.children || []).filter((child) => getChildGroupHealth(child) !== "ready").length;
  if (pendingChildren > 0) {
    return `当前一级分组下有 ${pendingChildren} 个二级分组仍需整理规则集。`;
  }
  return null;
}

function getChildGroupStatusText(group: RuleGroupNode): string {
  const health = getChildGroupHealth(group);
  if (health === "ready") return "规则集已就绪";
  if (health === "partial") return "存在待整理规则集";
  return "尚未绑定规则集";
}

function getChildGroupWarningText(group: RuleGroupNode): string | null {
  const health = getChildGroupHealth(group);
  if (health === "empty") {
    return "当前子类型还没有绑定任何规则集，上传后无法进入评查。";
  }
  if (health === "partial") {
    const inactiveCount = group.bindings.filter((item) => !item.is_active).length;
    const unpublishedCount = group.bindings.filter((item) => item.is_active && !item.has_usable_version).length;
    const zeroRuleCount = group.bindings.filter(
      (item) => item.is_active && item.has_usable_version && (item.usable_rule_count || 0) <= 0,
    ).length;
    const parts: string[] = [];
    if (inactiveCount > 0) parts.push(`${inactiveCount} 个绑定已停用`);
    if (unpublishedCount > 0) parts.push(`${unpublishedCount} 个绑定待发布`);
    if (zeroRuleCount > 0) parts.push(`${zeroRuleCount} 个绑定可用规则数为 0`);
    return `当前子类型存在异常配置：${parts.join("，")}。`;
  }
  return null;
}

function getBindingStatusText(binding: BindingItem): string {
  if (!binding.is_active) return "已停用";
  if (!binding.has_usable_version) return "待发布";
  if ((binding.usable_rule_count || 0) <= 0) return "可用规则数为 0";
  return "可运行";
}

function getBindingStatusClass(binding: BindingItem): "ok" | "warn" {
  return binding.is_active && binding.has_usable_version && (binding.usable_rule_count || 0) > 0 ? "ok" : "warn";
}

type RulePreviewState = {
  loading: boolean;
  loaded: boolean;
  error: string | null;
  rules: RuleSummary[];
};

type RuleDraftFormState = {
  groupId: number;
  yamlText: string;
  changeNote: string;
  template: RuleTemplatePayload | null;
  loading: boolean;
  saving: boolean;
  error: string | null;
  success: RuleDraftCreateResult | null;
};

function unwrapApiData<T>(payload: any): T {
  return (payload?.data ?? payload) as T;
}

function normalizeRuleTemplatePayload(payload: any): RuleTemplatePayload {
  const item = unwrapApiData<any>(payload) || {};
  const context = item.context || {};
  return {
    groupId: item.groupId ?? item.group_id ?? context.groupId ?? context.group_id,
    groupName: item.groupName ?? item.group_name ?? context.groupName ?? context.group_name,
    parentGroupName: item.parentGroupName ?? item.root_group_name ?? context.parentGroupName ?? context.parent_group_name,
    documentTypeName: item.documentTypeName ?? item.document_type_name ?? context.documentTypeName ?? context.document_type_name,
    entryModuleName: item.entryModuleName ?? item.entry_module_name ?? context.entryModuleName ?? context.entry_module_name,
    ruleType: item.ruleType ?? item.rule_type,
    ruleName: item.ruleName ?? item.rule_name,
    yamlTemplate: item.yamlTemplate ?? item.yaml_template,
    yamlText: item.yamlText ?? item.yaml_text,
    ossPreviewKey: item.ossPreviewKey ?? item.oss_preview_key,
  };
}

function normalizeRuleDraftCreateResult(payload: any): RuleDraftCreateResult {
  const item = unwrapApiData<any>(payload) || {};
  const createdVersion = item.createdVersion ?? item.created_version ?? {};
  const binding = item.binding ?? {};
  return {
    packId: item.packId ?? item.pack_id ?? item.groupId ?? item.group_id,
    groupId: item.groupId ?? item.group_id,
    groupName: item.groupName ?? item.group_name,
    ruleSetId: item.ruleSetId ?? item.rule_set_id ?? createdVersion.ruleSetId ?? createdVersion.rule_set_id,
    ruleSetName: item.ruleSetName ?? item.rule_set_name ?? binding.rule_name,
    ruleName: item.ruleName ?? item.rule_name ?? binding.rule_name,
    ruleType: item.ruleType ?? item.rule_type ?? binding.rule_type,
    versionId: item.versionId ?? item.version_id ?? createdVersion.id,
    versionNo: item.versionNo ?? item.version_no ?? createdVersion.versionNo ?? createdVersion.version_no,
    bindingId: item.bindingId ?? item.binding_id ?? binding.id,
  };
}

function findGroupNode(groups: RuleGroupNode[], groupId: number): RuleGroupNode | null {
  for (const root of groups) {
    if (root.id === groupId) return root;
    const child = (root.children || []).find((item) => item.id === groupId);
    if (child) return child;
  }
  return null;
}

function buildRuleDraftJumpUrl(group: RuleGroupNode | null, success: RuleDraftCreateResult | null, template: RuleTemplatePayload | null): string {
  if (success?.packId) {
    return `/rulesTest/detail?packId=${encodeURIComponent(String(success.packId))}`;
  }
  const params = new URLSearchParams();
  const keyword = success?.ruleType || template?.ruleType || group?.code || group?.name || "";
  if (keyword) params.set("keyword", keyword);
  return `/rulesTest/list${params.toString() ? `?${params.toString()}` : ""}`;
}

function GroupModal({
  visible,
  form,
  topGroups,
  docTypes,
  entryModules,
  onClose,
  onChange,
  onSubmit,
  saving,
}: {
  visible: boolean;
  form: GroupFormState;
  topGroups: RuleGroupNode[];
  docTypes: DocumentType[];
  entryModules: EntryModuleOption[];
  onClose: () => void;
  onChange: (patch: Partial<GroupFormState>) => void;
  onSubmit: () => void;
  saving: boolean;
}) {
  if (!visible) return null;
  const isRoot = form.pid === 0;
  const selectedParent = !isRoot ? topGroups.find((group) => group.id === form.pid) || null : null;
  const selectedRootDocTypeId = selectedParent?.document_type_id ? String(selectedParent.document_type_id) : "";
  const docTypeValue = isRoot ? form.documentTypeId : (selectedRootDocTypeId || form.documentTypeId);
  const entryModuleValue = isRoot ? form.entryModuleId : "";
  return (
    <div className="rg-modal-backdrop">
      <div className="rg-modal">
        <div className="rg-modal-header">
          <h3>{form.mode === "create" ? (isRoot ? "新增一级分组" : "新增二级分组") : "编辑分组"}</h3>
          <button type="button" className="icon-button" onClick={onClose}>
            <i className="ri-close-line"></i>
          </button>
        </div>
        <div className="rg-modal-body grid-form">
          <div className="form-item">
            <label>分组层级</label>
            <select
              value={String(form.pid)}
              onChange={(event) => {
                const nextPid = Number(event.target.value);
                const nextParent = topGroups.find((group) => group.id === nextPid);
                onChange({
                  pid: nextPid,
                  documentTypeId: nextPid === 0 ? form.documentTypeId : String(nextParent?.document_type_id || ""),
                  entryModuleId: nextPid === 0 ? form.entryModuleId : String(nextParent?.entry_module_id || ""),
                });
              }}
            >
              <option value="0">一级分组（业务大类）</option>
              {topGroups.map((group) => (
                <option key={group.id} value={group.id}>{`二级分组（上级：${group.name}）`}</option>
              ))}
            </select>
          </div>
          <div className="form-item">
            <label>分组名称</label>
            <input value={form.name} onChange={(event) => onChange({ name: event.target.value })} placeholder={isRoot ? "如：合同、卷宗、内部公文" : "如：建设工程合同、处罚-一般程序"} />
          </div>
          <div className="form-item">
            <label>分组编码</label>
            <input value={form.code} onChange={(event) => onChange({ code: event.target.value })} placeholder="请输入唯一编码" />
          </div>
          <div className="form-item">
            <label>排序</label>
            <input type="number" value={form.sortOrder} onChange={(event) => onChange({ sortOrder: Number(event.target.value || 0) })} />
          </div>
          <div className="form-item form-item-span-2">
            <label>关联入口模块</label>
            <select disabled={!isRoot} value={entryModuleValue} onChange={(event) => onChange({ entryModuleId: event.target.value })}>
              <option value="">{isRoot ? "可稍后绑定入口模块" : "继承上级入口模块"}</option>
              {entryModules.map((item) => (
                <option key={item.id} value={String(item.id)}>{item.name}</option>
              ))}
            </select>
            <p className="field-tip">
              {isRoot
                ? "一级分组承载业务大类，可先创建再补绑入口模块；例如合同、卷宗、后续新增业务。"
                : "二级分组通常继承上级入口模块，用来承接该大类下的具体业务类型。"}
            </p>
          </div>
          <div className="form-item form-item-span-2">
            <label>关联文档类型</label>
            <select disabled={isRoot ? false : !!selectedRootDocTypeId} value={docTypeValue} onChange={(event) => onChange({ documentTypeId: event.target.value })}>
              <option value="">{isRoot ? "一级分组可不直接绑定文档类型" : "请选择该二级对应的具体文档类型"}</option>
              {docTypes
                .filter((item) => item.isEnabled)
                .map((item) => (
                  <option key={item.id} value={String(item.id)}>{`${item.name} (${item.code})`}</option>
                ))}
            </select>
            <p className="field-tip">
              {isRoot
                ? "一级分组默认代表业务大类容器；这个字段只用于兼容旧数据，新建业务大类通常可留空。"
                : "二级分组对应实际业务类型，如建设工程合同、处罚-一般程序、许可-停业办理，再往下绑定运行规则集。"}
            </p>
          </div>
          <div className="form-item form-item-span-2">
            <label>说明</label>
            <textarea value={form.description} onChange={(event) => onChange({ description: event.target.value })} rows={4} placeholder="可选，说明这个分组的业务范围" />
          </div>
          <label className="switch-row form-item-span-2">
            <input type="checkbox" checked={form.isEnabled} onChange={(event) => onChange({ isEnabled: event.target.checked })} />
            <span>启用当前分组</span>
          </label>
        </div>
        <div className="rg-modal-footer">
          <Button type="default" onClick={onClose}>取消</Button>
          <Button type="primary" onClick={onSubmit} disabled={saving}>{saving ? "提交中..." : "保存"}</Button>
        </div>
      </div>
    </div>
  );
}

function BindingModal({
  visible,
  form,
  group,
  allRuleSets,
  siblingGroupCount,
  onClose,
  onChange,
  onSubmit,
  saving,
}: {
  visible: boolean;
  form: BindingFormState;
  group: RuleGroupNode | null;
  allRuleSets: RuleSetOption[];
  siblingGroupCount: number;
  onClose: () => void;
  onChange: (patch: Partial<BindingFormState>) => void;
  onSubmit: () => void;
  saving: boolean;
}) {
  const [keyword, setKeyword] = useState("");
  useEffect(() => {
    if (!visible) setKeyword("");
  }, [visible]);
  if (!visible || !group) return null;

  const usedIds = new Set(group.bindings.filter((item) => item.id !== form.bindingId).map((item) => item.rule_set_id));
  const filteredRuleSets = allRuleSets.filter((item) => {
    if (form.mode === "create" && usedIds.has(item.id)) return false;
    if (!keyword.trim()) return true;
    const target = `${item.ruleName} ${item.ruleType}`.toLowerCase();
    return target.includes(keyword.trim().toLowerCase());
  });

  return (
    <div className="rg-modal-backdrop">
      <div className="rg-modal large">
        <div className="rg-modal-header">
          <h3>{form.mode === "create" ? `为「${getSubtypeGroupDisplayName(group)}」绑定规则集` : `调整「${getSubtypeGroupDisplayName(group)}」绑定`}</h3>
          <button type="button" className="icon-button" onClick={onClose}>
            <i className="ri-close-line"></i>
          </button>
        </div>
        <div className="rg-modal-body grid-form">
          <div className="form-item form-item-span-2">
            <div className="modal-tip">
              <strong>{group.document_type_name || "未绑定文档类型"}</strong>
              <span>
                {group.entry_module_name || "未分配入口模块"}
                {siblingGroupCount > 1 ? ` · 当前一级共 ${siblingGroupCount} 个二级分组` : ""}
                {` · 当前组已绑定 ${group.bindings.length} 个规则集`}
              </span>
            </div>
          </div>
          <div className="form-item form-item-span-2">
            <div className="detail-alert info compact">
              <strong>这里配置的是运行绑定</strong>
              <span>当前弹窗维护的是“二级分组 → 规则集”的实际运行关系；文档类型页看到的只是规则集汇总，不代表每次上传都会全部命中。</span>
            </div>
          </div>
          <div className="form-item form-item-span-2">
            <label>搜索规则集</label>
            <input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="按规则集名称 / 类型搜索" />
          </div>
          <div className="form-item form-item-span-2">
            <label>规则集</label>
            <div className="rule-set-scroll-box">
              {filteredRuleSets.length > 0 ? filteredRuleSets.map((item) => {
                const selected = form.ruleSetId === String(item.id);
                return (
                  <button
                    key={item.id}
                    type="button"
                    className={`rule-set-option ${selected ? "active" : ""}`}
                    onClick={() => onChange({ ruleSetId: String(item.id) })}
                  >
                    <span>
                      <strong>{item.ruleName}</strong>
                      <em>{item.ruleType}</em>
                    </span>
                    <span className={`mini-badge ${item.hasUsableVersion ? "ok" : "warn"}`}>
                      {item.hasUsableVersion ? `可用规则数 ${item.usableRuleCount || 0}` : "未发布"}
                    </span>
                  </button>
                );
              }) : <div className="empty-block">没有匹配的规则集</div>}
            </div>
          </div>
          <div className="form-item">
            <label>优先级</label>
            <input type="number" value={form.priority} onChange={(event) => onChange({ priority: Number(event.target.value || 0) })} />
          </div>
          <label className="switch-row form-item">
            <input type="checkbox" checked={form.isActive} onChange={(event) => onChange({ isActive: event.target.checked })} />
            <span>启用当前绑定</span>
          </label>
          <div className="form-item form-item-span-2">
            <label>备注</label>
            <textarea rows={3} value={form.note} onChange={(event) => onChange({ note: event.target.value })} placeholder="可选，记录这个规则集在当前二级分组下的用途" />
          </div>
        </div>
        <div className="rg-modal-footer">
          <Button type="default" onClick={onClose}>取消</Button>
          <Button type="primary" onClick={onSubmit} disabled={saving}>{saving ? "提交中..." : "保存绑定"}</Button>
        </div>
      </div>
    </div>
  );
}

function RuleDraftModal({
  visible,
  group,
  form,
  onClose,
  onChange,
  onSubmit,
  onOpenRules,
}: {
  visible: boolean;
  group: RuleGroupNode | null;
  form: RuleDraftFormState;
  onClose: () => void;
  onChange: (patch: Partial<RuleDraftFormState>) => void;
  onSubmit: () => void;
  onOpenRules: () => void;
}) {
  if (!visible || !group) return null;
  const template = form.template;

  return (
    <div className="rg-modal-backdrop">
      <div className="rg-modal large">
        <div className="rg-modal-header">
          <h3>从二级分组新建规则集 / YAML</h3>
          <button type="button" className="icon-button" onClick={onClose}>
            <i className="ri-close-line"></i>
          </button>
        </div>
        <div className="rg-modal-body grid-form">
          <div className="form-item form-item-span-2">
            <div className="modal-tip">
              <strong>{getSubtypeGroupDisplayName(group)}</strong>
              <span>
                所属一级：{template?.parentGroupName || "-"} · 文档类型：{group.document_type_name || template?.documentTypeName || "-"}
                {` · 入口模块：${group.entry_module_name || template?.entryModuleName || "-"}`}
              </span>
            </div>
          </div>
          <div className="form-item form-item-span-2">
            <div className="detail-alert info compact">
              <strong>当前入口只负责生成完整 YAML 草稿</strong>
              <span>模板来自当前二级分组，保存后会创建规则集版本，并尽量同步回当前分组绑定。</span>
            </div>
          </div>
          <div className="form-item">
            <label>规则类型</label>
            <input value={template?.ruleType || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
          </div>
          <div className="form-item">
            <label>规则集名称</label>
            <input value={template?.ruleName || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
          </div>
          <div className="form-item form-item-span-2">
            <label>OSS 预览路径</label>
            <input value={template?.ossPreviewKey || ""} readOnly placeholder={form.loading ? "模板加载中..." : "等待后端返回"} />
            <p className="field-tip">后端正式保存时会按当前版本号写入实际规则文件路径。</p>
          </div>
          <div className="form-item form-item-span-2">
            <label>变更说明</label>
            <input
              value={form.changeNote}
              onChange={(event) => onChange({ changeNote: event.target.value, success: null })}
              placeholder="例如：初始化建设工程合同规则草稿"
            />
          </div>
          <div className="form-item form-item-span-2">
            <label>完整 YAML</label>
            <textarea
              rows={24}
              value={form.yamlText}
              onChange={(event) => onChange({ yamlText: event.target.value, error: null, success: null })}
              placeholder={form.loading ? "模板加载中..." : "这里显示后端生成的完整 YAML 模板"}
            />
            <p className="field-tip">这里直接提交完整 YAML 文本，不再由前端重组规则结构。</p>
          </div>
          {form.error ? (
            <div className="form-item form-item-span-2">
              <div className="detail-alert danger compact">
                <strong>保存失败</strong>
                <span>{form.error}</span>
              </div>
            </div>
          ) : null}
          {form.success ? (
            <div className="form-item form-item-span-2">
              <div className="detail-alert success compact">
                <strong>规则草稿已保存</strong>
                <span>
                  {form.success.ruleName || form.success.ruleSetName || template?.ruleName || "规则集"} ·
                  {` 版本 ${form.success.versionNo || form.success.versionId || "已创建"} `}
                  {form.success.bindingId ? `· 已同步绑定 #${form.success.bindingId}` : "· 绑定结果以后端返回为准"}
                </span>
              </div>
            </div>
          ) : null}
        </div>
        <div className="rg-modal-footer">
          <Button type="default" onClick={onClose}>关闭</Button>
          {form.success ? (
            <Button type="primary" onClick={onOpenRules}>继续进入规则页</Button>
          ) : (
            <Button type="primary" onClick={onSubmit} disabled={form.loading || form.saving}>
              {form.loading ? "模板加载中..." : form.saving ? "保存中..." : "保存规则草稿"}
            </Button>
          )}
        </div>
      </div>
    </div>
  );
}

export default function RuleGroupsIndex() {
  const { groups, docTypes, entryModules, ruleSets, frontendJWT } = useLoaderData<LoaderData>();
  const navigate = useNavigate();
  const { hasAnyPermission } = usePermission();
  const [searchParams, setSearchParams] = useSearchParams();
  const [groupTree, setGroupTree] = useState<RuleGroupNode[]>(groups);
  const topGroups = useMemo(() => toTopGroups(groupTree), [groupTree]);
  const nameValue = searchParams.get("name") || "";
  const codeValue = searchParams.get("code") || "";
  const statusValue = searchParams.get("status") || "";
  const moduleValue = searchParams.get("module") || "";
  const docTypeValue = searchParams.get("docType") || "";
  const healthValue = searchParams.get("health") || "";
  const [expandedGroupIds, setExpandedGroupIds] = useState<Set<number>>(new Set(topGroups.map((item) => item.id)));
  const [expandedBindingIds, setExpandedBindingIds] = useState<Set<number>>(new Set());
  const [rulePreviewMap, setRulePreviewMap] = useState<Record<number, RulePreviewState>>({});
  const [groupModalOpen, setGroupModalOpen] = useState(false);
  const [bindingModalOpen, setBindingModalOpen] = useState(false);
  const [draftModalOpen, setDraftModalOpen] = useState(false);
  const [saving, setSaving] = useState(false);
  const [groupForm, setGroupForm] = useState<GroupFormState>({
    mode: "create",
    pid: 0,
    name: "",
    code: "",
    description: "",
    documentTypeId: "",
    entryModuleId: "",
    sortOrder: 0,
    isEnabled: true,
  });
  const [bindingForm, setBindingForm] = useState<BindingFormState>({
    mode: "create",
    groupId: 0,
    ruleSetId: "",
    priority: 100,
    note: "",
    isActive: true,
  });
  const [activeBindingGroup, setActiveBindingGroup] = useState<RuleGroupNode | null>(null);
  const [activeDraftGroup, setActiveDraftGroup] = useState<RuleGroupNode | null>(null);
  const [draftForm, setDraftForm] = useState<RuleDraftFormState>({
    groupId: 0,
    yamlText: "",
    changeNote: "",
    template: null,
    loading: false,
    saving: false,
    error: null,
    success: null,
  });
  const mountedRef = useRef(false);
  const childGroupStats = useMemo(() => buildChildGroupStats(topGroups), [topGroups]);
  const canManageGroups = hasAnyPermission(["evaluation_group:create:write", "evaluation_group:update:write"]);
  const canManageBindings = hasAnyPermission(["evaluation_group:update:write"]);
  const canCreateRuleDraft = hasAnyPermission(["evaluation_group:update:write", "rules:create:write"]);

  const filteredGroups = useMemo(() => {
    return topGroups
      .map((root) => {
        const rootNameTarget = [root.name, root.entry_module_name || "", root.document_type_name || ""].join(" ").toLowerCase();
        const rootCodeTarget = [root.code, root.entry_module_name || ""].join(" ").toLowerCase();
        const rootMatchesName = !nameValue.trim() || rootNameTarget.includes(nameValue.trim().toLowerCase());
        const rootMatchesCode = !codeValue.trim() || rootCodeTarget.includes(codeValue.trim().toLowerCase());
        const rootMatchesModule = !moduleValue || String(root.entry_module_id || "") === moduleValue;
        const rootMatchesStatus = statusValue === "enabled" ? root.is_enabled : statusValue === "disabled" ? !root.is_enabled : true;
        const children = (root.children || []).filter((child) => {
          if (moduleValue && String(child.entry_module_id || "") !== moduleValue) return false;
          if (docTypeValue && String(child.document_type_id || "") !== docTypeValue) return false;
          if (statusValue === "enabled" && !child.is_enabled) return false;
          if (statusValue === "disabled" && child.is_enabled) return false;
          if (healthValue && getChildGroupHealth(child) !== healthValue) return false;
          if (nameValue.trim()) {
            const target = [root.name, child.name, child.document_type_name || "", ...child.bindings.map((item) => item.rule_name || "")].join(" ").toLowerCase();
            if (!target.includes(nameValue.trim().toLowerCase())) return false;
          }
          if (codeValue.trim()) {
            const target = [root.code, child.code, child.document_type_name || "", ...child.bindings.map((item) => item.rule_type || "")].join(" ").toLowerCase();
            if (!target.includes(codeValue.trim().toLowerCase())) return false;
          }
          return true;
        });
        const shouldKeepRootWithoutChildren = !docTypeValue && !healthValue && children.length === 0 && rootMatchesName && rootMatchesCode && rootMatchesModule && rootMatchesStatus;
        if (!children.length && !shouldKeepRootWithoutChildren) return null;
        return { ...root, children };
      })
      .filter(Boolean) as RuleGroupNode[];
  }, [codeValue, docTypeValue, healthValue, moduleValue, nameValue, statusValue, topGroups]);

  const totalVisibleRows = filteredGroups.reduce((sum, root) => {
    const childRows = root.children?.length || 0;
    const bindingRows = expandedGroupIds.has(root.id)
      ? (root.children || []).reduce((bindingSum, child) => bindingSum + child.bindings.length, 0)
      : 0;
    return sum + 1 + childRows + bindingRows;
  }, 0);

  const summaryMetrics = useMemo(() => {
    const childGroups = topGroups.flatMap((root) => root.children || []);
    return {
      rootCount: topGroups.length,
      childCount: childGroups.length,
      readyCount: childGroups.filter((item) => getChildGroupHealth(item) === "ready").length,
      warningCount: childGroups.filter((item) => getChildGroupHealth(item) !== "ready").length,
      unboundRootCount: topGroups.filter((item) => !item.entry_module_id).length,
    };
  }, [topGroups]);

  const handleParamChange = (key: string, value: string) => {
    const next = new URLSearchParams(searchParams);
    if (value) next.set(key, value);
    else next.delete(key);
    setSearchParams(next);
  };

  const resetFilters = () => setSearchParams(new URLSearchParams());

  const reloadPage = async (): Promise<RuleGroupNode[]> => {
    const nextGroups = await fetchGroupTree(frontendJWT);
    setGroupTree(nextGroups);
    setActiveBindingGroup((current) => (current ? findGroupNode(nextGroups, current.id) : null));
    setActiveDraftGroup((current) => (current ? findGroupNode(nextGroups, current.id) : null));
    return nextGroups;
  };

  const handleApiError = (error: any) => {
    const message = error?.response?.data?.msg || error?.response?.data?.detail || error?.message || "操作失败";
    window.alert(String(message));
  };

  const openCreateRoot = () => {
    setGroupForm({ mode: "create", pid: 0, name: "", code: "", description: "", documentTypeId: "", entryModuleId: "", sortOrder: 0, isEnabled: true });
    setGroupModalOpen(true);
  };

  const openCreateChild = (root: RuleGroupNode) => {
    setGroupForm({
      mode: "create",
      pid: root.id,
      name: "",
      code: "",
      description: "",
      documentTypeId: root.document_type_id ? String(root.document_type_id) : "",
      entryModuleId: root.entry_module_id ? String(root.entry_module_id) : "",
      sortOrder: 0,
      isEnabled: true,
    });
    setGroupModalOpen(true);
  };

  const openEditGroup = (group: RuleGroupNode) => {
    setGroupForm({
      id: group.id,
      mode: "edit",
      pid: group.pid,
      name: group.name,
      code: group.code,
      description: group.description || "",
      documentTypeId: group.document_type_id ? String(group.document_type_id) : "",
      entryModuleId: group.entry_module_id ? String(group.entry_module_id) : "",
      sortOrder: group.sort_order || 0,
      isEnabled: group.is_enabled,
    });
    setGroupModalOpen(true);
  };

  const openCreateBinding = (group: RuleGroupNode) => {
    setActiveBindingGroup(group);
    setBindingForm({ mode: "create", groupId: group.id, ruleSetId: "", priority: 100, note: "", isActive: true });
    setBindingModalOpen(true);
  };

  const openCreateRuleDraft = async (group: RuleGroupNode) => {
    setActiveDraftGroup(group);
    setDraftForm({
      groupId: group.id,
      yamlText: "",
      changeNote: "",
      template: null,
      loading: true,
      saving: false,
      error: null,
      success: null,
    });
    setDraftModalOpen(true);
    try {
      const response = await axios.get(`${API_BASE_URL}/api/v3/evaluation-point-groups/${group.id}/rule-template`, {
        headers: authHeaders(frontendJWT),
      });
      const template = normalizeRuleTemplatePayload(response?.data);
      const yamlText = String(template?.yamlTemplate || template?.yamlText || "");
      setDraftForm((prev) => ({
        ...prev,
        loading: false,
        template,
        yamlText,
        changeNote: prev.changeNote || `初始化 ${getSubtypeGroupDisplayName(group)} 规则草稿`,
        error: yamlText.trim() ? null : "后端未返回可编辑的 YAML 模板",
      }));
    } catch (error: any) {
      const message = error?.response?.data?.msg || error?.response?.data?.detail || error?.message || "模板加载失败";
      setDraftForm((prev) => ({ ...prev, loading: false, error: String(message) }));
    }
  };

  const openEditBinding = (group: RuleGroupNode, binding: BindingItem) => {
    setActiveBindingGroup(group);
    setBindingForm({
      mode: "edit",
      groupId: group.id,
      bindingId: binding.id,
      ruleSetId: String(binding.rule_set_id),
      priority: binding.priority,
      note: binding.note || "",
      isActive: binding.is_active,
    });
    setBindingModalOpen(true);
  };

  const submitGroup = async () => {
    if (!groupForm.name.trim()) return window.alert("请填写分组名称");
    if (!groupForm.code.trim()) return window.alert("请填写分组编码");
    if (groupForm.pid !== 0 && !groupForm.documentTypeId) return window.alert("二级分组必须绑定具体文档类型");
    setSaving(true);
    try {
      const payload = {
        name: groupForm.name.trim(),
        code: groupForm.code.trim(),
        pid: groupForm.pid === 0 ? 0 : groupForm.pid,
        description: groupForm.description.trim() || null,
        document_type_id: groupForm.documentTypeId ? Number(groupForm.documentTypeId) : null,
        entry_module_id: groupForm.entryModuleId ? Number(groupForm.entryModuleId) : null,
        sort_order: groupForm.sortOrder,
        is_enabled: groupForm.isEnabled,
      };
      if (groupForm.mode === "create") {
        await axios.post(`${API_BASE_URL}/api/v3/evaluation-point-groups`, payload, { headers: { ...authHeaders(frontendJWT) } });
      } else {
        await axios.put(`${API_BASE_URL}/api/v3/evaluation-point-groups/${groupForm.id}`, payload, { headers: { ...authHeaders(frontendJWT) } });
      }
      await reloadPage();
      setGroupModalOpen(false);
    } catch (error) {
      handleApiError(error);
    } finally {
      setSaving(false);
    }
  };

  const submitBinding = async () => {
    if (!bindingForm.ruleSetId) return window.alert("请选择规则集");
    setSaving(true);
    try {
      const payload = {
        rule_set_id: Number(bindingForm.ruleSetId),
        priority: bindingForm.priority,
        note: bindingForm.note.trim() || null,
        is_active: bindingForm.isActive,
      };
      if (bindingForm.mode === "create") {
        await axios.post(`${API_BASE_URL}/api/v3/evaluation-point-groups/${bindingForm.groupId}/bindings`, payload, { headers: { ...authHeaders(frontendJWT) } });
      } else {
        await axios.put(`${API_BASE_URL}/api/v3/evaluation-point-groups/bindings/${bindingForm.bindingId}`, payload, { headers: { ...authHeaders(frontendJWT) } });
      }
      await reloadPage();
      setBindingModalOpen(false);
    } catch (error) {
      handleApiError(error);
    } finally {
      setSaving(false);
    }
  };

  const deleteGroup = async (group: RuleGroupNode) => {
    const label = group.pid === 0 ? `一级分组「${group.name}」` : `二级分组「${group.name}」`;
    if (!window.confirm(`确认删除${label}吗？`)) return;
    try {
      const response = await axios.delete(`${API_BASE_URL}/api/v3/evaluation-point-groups/${group.id}`, { headers: authHeaders(frontendJWT) });
      const payload = response?.data?.data ?? response?.data;
      if (payload?.success === false) return window.alert(payload?.message || "删除失败");
      await reloadPage();
    } catch (error) {
      handleApiError(error);
    }
  };

  const deleteBinding = async (binding: BindingItem) => {
    if (!window.confirm(`确认解除规则集「${binding.rule_name || binding.rule_type || binding.rule_set_id}」吗？`)) return;
    try {
      await axios.delete(`${API_BASE_URL}/api/v3/evaluation-point-groups/bindings/${binding.id}`, { headers: authHeaders(frontendJWT) });
      await reloadPage();
    } catch (error) {
      handleApiError(error);
    }
  };

  const submitRuleDraft = async () => {
    if (!activeDraftGroup) return;
    if (!draftForm.yamlText.trim()) {
      setDraftForm((prev) => ({ ...prev, error: "请先确认 YAML 内容后再保存" }));
      return;
    }
    setDraftForm((prev) => ({ ...prev, saving: true, error: null }));
    try {
      const payload = {
        yaml_text: draftForm.yamlText,
        change_note: draftForm.changeNote.trim() || null,
      };
      const response = await axios.post(
        `${API_BASE_URL}/api/v3/evaluation-point-groups/${activeDraftGroup.id}/rule-drafts`,
        payload,
        { headers: authHeaders(frontendJWT) },
      );
      const result = normalizeRuleDraftCreateResult(response?.data);
      const nextGroups = await reloadPage();
      const refreshedGroup = findGroupNode(nextGroups, activeDraftGroup.id) || activeDraftGroup;
      setActiveDraftGroup(refreshedGroup);
      setDraftForm((prev) => ({ ...prev, saving: false, success: result }));
    } catch (error: any) {
      const message = error?.response?.data?.msg || error?.response?.data?.detail || error?.message || "规则草稿保存失败";
      setDraftForm((prev) => ({ ...prev, saving: false, error: String(message) }));
    }
  };

  const openRuleDraftResult = () => {
    const target = buildRuleDraftJumpUrl(activeDraftGroup, draftForm.success, draftForm.template);
    navigate(target);
  };

  const toggleRoot = (groupId: number) => {
    setExpandedGroupIds((prev) => {
      const next = new Set(prev);
      if (next.has(groupId)) next.delete(groupId);
      else next.add(groupId);
      return next;
    });
  };

  const loadRulePreview = async (binding: BindingItem) => {
    if (rulePreviewMap[binding.id]?.loaded || rulePreviewMap[binding.id]?.loading) return;
    const versionId = binding.current_version_id || binding.fallback_version_id;
    if (!versionId) {
      setRulePreviewMap((prev) => ({ ...prev, [binding.id]: { loading: false, loaded: true, error: "当前规则集没有可读取版本", rules: [] } }));
      return;
    }
    setRulePreviewMap((prev) => ({ ...prev, [binding.id]: { loading: true, loaded: false, error: null, rules: [] } }));
    try {
      const response = await axios.get(`${API_BASE_URL}/api/rule-sets/versions/${versionId}/content`, { headers: authHeaders(frontendJWT) });
      const payload = response?.data?.data ?? response?.data ?? null;
      const yamlText = typeof payload?.yamlText === "string" ? payload.yamlText : "";
      const rules = yamlText.trim() ? parseRuleSummariesFromYaml(yamlText) : [];
      setRulePreviewMap((prev) => ({ ...prev, [binding.id]: { loading: false, loaded: true, error: yamlText.trim() ? null : "规则 YAML 为空", rules } }));
    } catch (error: any) {
      const message = error?.response?.data?.msg || error?.message || "规则内容加载失败";
      setRulePreviewMap((prev) => ({ ...prev, [binding.id]: { loading: false, loaded: true, error: String(message), rules: [] } }));
    }
  };

  const toggleBindingPreview = (binding: BindingItem) => {
    setExpandedBindingIds((prev) => {
      const next = new Set(prev);
      if (next.has(binding.id)) next.delete(binding.id);
      else next.add(binding.id);
      return next;
    });
  };

  useEffect(() => {
    if (!mountedRef.current) {
      mountedRef.current = true;
      return;
    }
    expandedBindingIds.forEach((bindingId) => {
      for (const root of filteredGroups) {
        const match = (root.children || []).flatMap((child) => child.bindings).find((item) => item.id === bindingId);
        if (match) {
          void loadRulePreview(match);
          break;
        }
      }
    });
  }, [expandedBindingIds, filteredGroups]);

  return (
    <div className="rule-groups-page">
      <div className="page-header">
        <div>
          <h2 className="page-title">
            评查点分组管理
            <span className="page-count">分组数：{totalVisibleRows}</span>
          </h2>
          <p className="page-subtitle">把“业务大类 → 具体业务类型 → 实际运行规则集”放在同一页看清楚，避免把入口挂载、类型主数据和运行绑定混在一起。</p>
        </div>
        <div className="page-actions">
          <Button type="default" icon="ri-expand-up-down-line" onClick={() => setExpandedGroupIds(new Set(filteredGroups.map((item) => item.id)))}>展开全部</Button>
          <Button type="default" icon="ri-collapse-diagonal-line" onClick={() => { setExpandedGroupIds(new Set()); setExpandedBindingIds(new Set()); }}>收起全部</Button>
          {canManageGroups ? (
            <Button type="primary" icon="ri-add-line" onClick={openCreateRoot}>新增一级分组</Button>
          ) : null}
        </div>
      </div>

      <Card className="filter-card">
        <div className="summary-metrics">
          <div className="metric-box">
            <span className="metric-label">一级分组</span>
            <strong>{summaryMetrics.rootCount}</strong>
            <em>{summaryMetrics.unboundRootCount > 0 ? `其中 ${summaryMetrics.unboundRootCount} 个待绑定入口模块` : "业务大类容器，可绑定入口模块"}</em>
          </div>
          <div className="metric-box">
            <span className="metric-label">二级分组</span>
            <strong>{summaryMetrics.childCount}</strong>
            <em>上传时实际命中的具体业务类型</em>
          </div>
          <div className="metric-box">
            <span className="metric-label">已就绪二级分组</span>
            <strong>{summaryMetrics.readyCount}</strong>
            <em>已绑定可运行规则集</em>
          </div>
          <div className="metric-box warning">
            <span className="metric-label">待处理二级分组</span>
            <strong>{summaryMetrics.warningCount}</strong>
            <em>未绑定 / 待发布 / 停用</em>
          </div>
        </div>
        <div className="logic-strip">
          <div className="logic-item">
            <strong>一级分组</strong>
            <span>对应业务大类，如合同、卷宗，并可绑定到入口模块</span>
          </div>
          <div className="logic-arrow">→</div>
          <div className="logic-item">
            <strong>二级分组</strong>
            <span>对应该大类下的具体业务类型，如建设工程合同、处罚-一般程序</span>
          </div>
          <div className="logic-arrow">→</div>
          <div className="logic-item">
            <strong>规则集</strong>
            <span>规则集挂在二级分组下，入口上传时按实际二级分组命中</span>
          </div>
        </div>
      </Card>

      <Card className="filter-card">
        <div className="filter-row">
          <div className="filter-item">
            <label>分组名称</label>
            <input value={nameValue} onChange={(event) => handleParamChange("name", event.target.value)} placeholder="搜索一级分组 / 二级分组 / 规则集" />
          </div>
          <div className="filter-item">
            <label>分组编码</label>
            <input value={codeValue} onChange={(event) => handleParamChange("code", event.target.value)} placeholder="搜索分组编码或规则类型" />
          </div>
          <div className="filter-item">
            <label>入口模块</label>
            <select value={moduleValue} onChange={(event) => handleParamChange("module", event.target.value)}>
              <option value="">全部</option>
              {entryModules.map((item) => <option key={item.id} value={String(item.id)}>{item.name}</option>)}
            </select>
          </div>
          <div className="filter-item">
            <label>文档类型</label>
            <select value={docTypeValue} onChange={(event) => handleParamChange("docType", event.target.value)}>
              <option value="">全部</option>
              {docTypes.filter((item) => item.isEnabled).map((item) => (
                <option key={item.id} value={String(item.id)}>{item.name}</option>
              ))}
            </select>
          </div>
          <div className="filter-item filter-small">
            <label>状态</label>
            <select value={statusValue} onChange={(event) => handleParamChange("status", event.target.value)}>
              <option value="">全部</option>
              <option value="enabled">启用</option>
              <option value="disabled">禁用</option>
            </select>
          </div>
          <div className="filter-item">
            <label>运行配置</label>
            <select value={healthValue} onChange={(event) => handleParamChange("health", event.target.value)}>
              <option value="">全部</option>
              <option value="ready">规则集已就绪</option>
              <option value="partial">存在待整理规则集</option>
              <option value="empty">未绑定规则集</option>
            </select>
          </div>
          <div className="filter-actions-row">
            <Button type="default" icon="ri-refresh-line" onClick={resetFilters}>重置</Button>
          </div>
        </div>
      </Card>

      <Card className="table-card">
        <div className="table-wrap">
          <table className="group-table">
            <thead>
              <tr>
                <th>配置对象</th>
                <th>业务定位</th>
                <th>运行规则</th>
                <th>状态</th>
                <th>更新时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              {filteredGroups.length > 0 ? filteredGroups.map((root) => {
                const expanded = expandedGroupIds.has(root.id);
                return (
                  <Fragment key={root.id}>
                    <tr className="group-row level-root">
                      <td>
                        <button type="button" className="name-cell-button" onClick={() => toggleRoot(root.id)}>
                          <span className="expand-icon"><i className={expanded ? "ri-arrow-down-s-line" : "ri-arrow-right-s-line"}></i></span>
                          <span className="name-cell-text">
                            <strong>{root.name}</strong>
                            <em>{getRootGroupSubtitle(root)}</em>
                          </span>
                        </button>
                      </td>
                      <td>
                        <div className="meta-cell">
                          <strong>{root.entry_module_name ? `入口模块：${root.entry_module_name}` : (root.document_type_name || root.name)}</strong>
                          <span>{getRootGroupDescription(root)}</span>
                          <span>下属二级分组 {(root.children || []).length} 个 · 编码 {root.code}</span>
                        </div>
                      </td>
                      <td>
                        <div className="meta-cell">
                          <strong>不直接挂运行规则</strong>
                          <span>实际运行以二级分组绑定规则集为准</span>
                        </div>
                      </td>
                      <td>
                        <span className={`status-badge ${root.is_enabled ? "enabled" : "disabled"}`}>{root.is_enabled ? "启用" : "禁用"}</span>
                        <span className={`mini-badge ${getRootGroupHealth(root) === "ready" ? "ok" : "warn"}`}>{getRootGroupStatusText(root)}</span>
                        {getRootGroupWarningText(root) ? (
                          <div className="warning-inline">{getRootGroupWarningText(root)}</div>
                        ) : (
                          <div className="hint-inline">当前一级分组已具备完整入口链路。</div>
                        )}
                      </td>
                      <td>{formatDateTime(root.updated_at || root.created_at)}</td>
                      <td>
                        <div className="action-links">
                          {canManageGroups ? (
                            <button type="button" className="action-link button-link" onClick={() => openCreateChild(root)}>新增二级分组</button>
                          ) : null}
                          {canManageGroups ? (
                            <button type="button" className="action-link button-link" onClick={() => openEditGroup(root)}>编辑</button>
                          ) : null}
                          {canManageGroups ? (
                            <button type="button" className="action-link danger button-link" onClick={() => deleteGroup(root)}>删除</button>
                          ) : null}
                        </div>
                      </td>
                    </tr>
                    {expanded ? (root.children || []).map((child) => (
                      <Fragment key={child.id}>
                        <tr className="group-row level-child">
                          <td>
                            <div className="name-cell-button child static">
                              <span className="expand-icon placeholder"></span>
                              <span className="name-cell-text">
                                <strong>{getSubtypeGroupDisplayName(child)}</strong>
                                <em>
                                  二级分组 · {child.document_type_name || "未绑定文档类型"}
                                  {child.entry_module_name ? ` · ${child.entry_module_name}` : ""}
                                  {(childGroupStats[child.id]?.siblingCount || 0) > 1 ? ` · 第 ${childGroupStats[child.id].siblingIndex} 个二级分组` : ""}
                                </em>
                              </span>
                            </div>
                          </td>
                          <td>
                            <div className="meta-cell">
                              <strong>{child.document_type_name || "未绑定文档类型"}</strong>
                              <span>所属一级：{root.name} · 编码 {child.code}</span>
                            </div>
                          </td>
                          <td>
                            <div className="meta-cell">
                              <strong>{getChildGroupStatusText(child)}</strong>
                              <span>
                                已绑规则集 {child.bindings.length} 个 · 已就绪 {getChildGroupReadyBindingCount(child)} 个 · 可用规则数 {getChildGroupUsableRuleCount(child)}
                              </span>
                            </div>
                          </td>
                          <td>
                            <span className={`status-badge ${child.is_enabled ? "enabled" : "disabled"}`}>{child.is_enabled ? "启用" : "禁用"}</span>
                            <span className={`mini-badge ${getChildGroupHealth(child) === "ready" ? "ok" : "warn"}`}>
                              {getChildGroupHealth(child) === "ready" ? "可运行" : getChildGroupHealth(child) === "partial" ? "待整理" : "未绑定"}
                            </span>
                            {getChildGroupWarningText(child) ? (
                              <div className="warning-inline">{getChildGroupWarningText(child)}</div>
                            ) : (
                              <div className="hint-inline">当前子类型规则配置完整，可直接用于上传评查。</div>
                            )}
                          </td>
                          <td>{formatDateTime(child.updated_at || child.created_at)}</td>
                          <td>
                            <div className="action-links">
                              <button
                                type="button"
                                className="action-link button-link"
                                onClick={() => {
                                  setExpandedBindingIds((prev) => {
                                    const next = new Set(prev);
                                    const isExpanded = child.bindings.every((item) => next.has(item.id));
                                    child.bindings.forEach((item) => {
                                      if (isExpanded) next.delete(item.id);
                                      else next.add(item.id);
                                    });
                                    return next;
                                  });
                                }}
                              >
                                {child.bindings.length ? (child.bindings.every((item) => expandedBindingIds.has(item.id)) ? "收起规则集" : "查看规则集") : "暂无规则集"}
                              </button>
                              {canCreateRuleDraft ? (
                                <button type="button" className="action-link button-link" onClick={() => openCreateRuleDraft(child)}>新建规则集/YAML</button>
                              ) : null}
                              {canManageBindings ? (
                                <button type="button" className="action-link button-link" onClick={() => openCreateBinding(child)}>绑定规则集</button>
                              ) : null}
                              {canManageGroups ? (
                                <button type="button" className="action-link button-link" onClick={() => openEditGroup(child)}>编辑</button>
                              ) : null}
                              {canManageGroups ? (
                                <button type="button" className="action-link danger button-link" onClick={() => deleteGroup(child)}>删除</button>
                              ) : null}
                            </div>
                          </td>
                        </tr>
                        {child.bindings.map((binding) => {
                          const expandedBinding = expandedBindingIds.has(binding.id);
                          const previewState = rulePreviewMap[binding.id];
                          return (
                            <Fragment key={binding.id}>
                              <tr className="group-row level-binding">
                                <td>
                                  <button type="button" className="name-cell-button child" onClick={() => toggleBindingPreview(binding)}>
                                    <span className="expand-icon">
                                      <i className={expandedBinding ? "ri-arrow-down-s-line" : "ri-arrow-right-s-line"}></i>
                                    </span>
                                    <span className="name-cell-text">
                                      <strong>{binding.rule_name || `规则集 #${binding.rule_set_id}`}</strong>
                                      <em>{binding.rule_type || "未标记规则类型"} · {formatVersionLabel(binding)}</em>
                                    </span>
                                  </button>
                                </td>
                                <td>
                                  <div className="meta-cell">
                                    <strong>挂载到：{getSubtypeGroupDisplayName(child)}</strong>
                                    <span>优先级 {binding.priority} · {binding.note || "未填写运行备注"}</span>
                                  </div>
                                </td>
                                <td>
                                  <div className="meta-cell">
                                    <strong>{getBindingStatusText(binding)}</strong>
                                    <span>可用规则数 {binding.usable_rule_count || 0} · 运行绑定 #{binding.rule_type_binding_id || "-"}</span>
                                  </div>
                                </td>
                                <td>
                                  <span className={`status-badge ${binding.is_active ? "enabled" : "disabled"}`}>{binding.is_active ? "启用" : "停用"}</span>
                                  <span className={`mini-badge ${getBindingStatusClass(binding)}`}>{getBindingStatusText(binding)}</span>
                                </td>
                                <td>-</td>
                                <td>
                                  <div className="action-links">
                                    <button type="button" className="action-link button-link" onClick={() => toggleBindingPreview(binding)}>
                                      {expandedBinding ? "收起规则明细" : "查看组内规则"}
                                    </button>
                                    {canManageBindings ? (
                                      <button type="button" className="action-link button-link" onClick={() => openEditBinding(child, binding)}>编辑绑定</button>
                                    ) : null}
                                    {canManageBindings ? (
                                      <button type="button" className="action-link danger button-link" onClick={() => deleteBinding(binding)}>解除绑定</button>
                                    ) : null}
                                  </div>
                                </td>
                              </tr>
                              {expandedBinding ? (
                                <tr className="preview-row">
                                  <td colSpan={6}>
                                    <div className="preview-panel">
                                      <div className="preview-header-row">
                                        <strong>{binding.rule_name || binding.rule_type || `规则集 #${binding.rule_set_id}`}</strong>
                                        <span>{formatVersionLabel(binding)} · 可用规则数 {binding.usable_rule_count || 0}</span>
                                      </div>
                                      {previewState?.loading ? (
                                        <div className="preview-empty">规则内容加载中...</div>
                                      ) : previewState?.error ? (
                                        <div className="preview-empty">{previewState.error}</div>
                                      ) : previewState?.rules?.length ? (
                                        <div className="preview-table-wrap">
                                          <table className="preview-table">
                                            <thead>
                                              <tr>
                                                <th>规则编码</th>
                                                <th>规则名称</th>
                                                <th>风险等级</th>
                                                <th>命中说明</th>
                                              </tr>
                                            </thead>
                                            <tbody>
                                              {previewState.rules.map((rule, index) => (
                                                <tr key={`${binding.id}-${rule.code || index}`}>
                                                  <td>{rule.code || "-"}</td>
                                                  <td>{rule.name || "-"}</td>
                                                  <td>{rule.severity || "-"}</td>
                                                  <td>{rule.description || rule.scope || "-"}</td>
                                                </tr>
                                              ))}
                                            </tbody>
                                          </table>
                                        </div>
                                      ) : (
                                        <div className="preview-empty">当前规则集暂无可展示规则明细</div>
                                      )}
                                    </div>
                                  </td>
                                </tr>
                              ) : null}
                            </Fragment>
                          );
                        })}
                      </Fragment>
                    )) : null}
                  </Fragment>
                );
              }) : (
                <tr>
                  <td colSpan={6} className="empty-cell">暂无匹配的分组数据</td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </Card>

      <GroupModal
        visible={groupModalOpen}
        form={groupForm}
        topGroups={topGroups}
        docTypes={docTypes}
        entryModules={entryModules}
        onClose={() => setGroupModalOpen(false)}
        onChange={(patch) => setGroupForm((prev) => ({ ...prev, ...patch }))}
        onSubmit={submitGroup}
        saving={saving}
      />
      <BindingModal
        visible={bindingModalOpen}
        form={bindingForm}
        group={activeBindingGroup}
        allRuleSets={ruleSets}
        siblingGroupCount={activeBindingGroup ? (childGroupStats[activeBindingGroup.id]?.siblingCount || 1) : 1}
        onClose={() => setBindingModalOpen(false)}
        onChange={(patch) => setBindingForm((prev) => ({ ...prev, ...patch }))}
        onSubmit={submitBinding}
        saving={saving}
      />
      <RuleDraftModal
        visible={draftModalOpen}
        group={activeDraftGroup}
        form={draftForm}
        onClose={() => setDraftModalOpen(false)}
        onChange={(patch) => setDraftForm((prev) => ({ ...prev, ...patch }))}
        onSubmit={submitRuleDraft}
        onOpenRules={openRuleDraftResult}
      />
    </div>
  );
}
