<template>
  <div class="policy-page">
    <div class="secondary-overlay" v-if="!secondaryVerified">
      <div class="secondary-card">
        <h3>二级密码校验</h3>
        <p class="muted">请输入管理员二级密码后继续配置策略。</p>
        <input
          class="secondary-input"
          type="password"
          v-model="secondaryPassword"
          :disabled="secondaryLoading"
          placeholder="二级密码"
          @keyup.enter="handleVerifySecondary"
        />
        <div class="secondary-actions">
          <button
            type="button"
            class="primary"
            :disabled="secondaryLoading"
            @click="handleVerifySecondary"
          >
            {{ secondaryLoading ? '校验中...' : '确认' }}
          </button>
          <button
            type="button"
            class="ghost-btn"
            :disabled="secondaryLoading"
            @click="checkSecondary"
          >
            重新检测
          </button>
        </div>
        <p v-if="secondaryError" class="secondary-error">{{ secondaryError }}</p>
      </div>
    </div>
    <header class="policy-header">
      <div>
        <h1>管理员策略配置</h1>
        <p>控制工具分类、模型与前端功能禁用。最新保存：{{ lastUpdated || '—' }}</p>
      </div>
      <div class="header-actions">
        <div class="dropdown" :class="{ open: targetMenuOpen }">
          <button type="button" class="ghost-btn" @click="toggleTargetMenu">
            <span>{{ targetTypeLabel }}</span>
            <span class="caret">▾</span>
          </button>
          <div class="dropdown-menu" v-if="targetMenuOpen">
            <button
              v-for="opt in targetOptions"
              :key="opt.value"
              type="button"
              @click="pickTargetType(opt.value)"
            >
              {{ opt.label }}
            </button>
          </div>
        </div>
        <div v-if="form.target_type !== 'global'" class="text-field">
          <input
            v-model="form.target_value"
            :placeholder="targetPlaceholder"
            class="target-input"
            type="text"
          />
        </div>
        <button type="button" class="ghost-btn" @click="loadScope">载入</button>
        <button type="button" class="primary" :disabled="saving" @click="savePolicy">
          {{ saving ? '保存中...' : '保存' }}
        </button>
      </div>
    </header>

    <transition name="fade">
      <div v-if="banner.message" class="banner" :class="banner.type">
        <span>{{ banner.message }}</span>
        <button type="button" class="banner-close" @click="banner.message = ''">×</button>
      </div>
    </transition>

    <section class="panel">
      <div class="panel-title">
        <h2>工具分类</h2>
        <button type="button" @click="addCategory">新增分类</button>
      </div>
      <div class="category-table">
        <div class="category-row category-head">
          <span>分类 ID</span>
          <span>名称</span>
          <span>工具列表</span>
          <span>默认启用</span>
          <span>强制状态</span>
          <span>操作</span>
        </div>
        <div class="category-row" v-for="cat in categoryList" :key="cat.id">
          <input v-model="cat.id" class="id-input" />
          <input v-model="cat.label" />
          <div class="tool-select" :class="{ open: openToolMenu === cat.id }">
            <button type="button" class="tool-select-trigger" @click.stop="toggleToolMenu(cat.id)">
              <span v-if="cat.tools.length" class="tool-badges">
                <span class="tool-badge" v-for="tool in cat.tools" :key="tool">{{ tool }}</span>
              </span>
              <span v-else class="muted">选择工具</span>
              <span class="caret">▾</span>
            </button>
            <div class="tool-select-menu" v-if="openToolMenu === cat.id" @click.stop>
              <div class="tool-select-search">
                <input v-model="toolSearch" type="text" placeholder="搜索或添加工具 ID" />
              </div>
              <div class="tool-select-options">
                <label v-for="tool in filteredToolOptions(cat)" :key="tool">
                  <input
                    type="checkbox"
                    :checked="cat.tools.includes(tool)"
                    @change="toggleToolInCategory(cat, tool)"
                  />
                  <span>{{ tool }}</span>
                </label>
                <p v-if="!filteredToolOptions.length" class="muted tiny">未找到匹配的工具</p>
              </div>
              <button
                v-if="toolSearch.trim() && !toolOptionsSet.has(toolSearch.trim())"
                type="button"
                class="link small"
                @click="addCustomTool(cat)"
              >
                添加自定义工具 “{{ toolSearch.trim() }}”
              </button>
            </div>
          </div>
          <label class="toggle-row compact">
            <input
              type="checkbox"
              :checked="getCategoryDefault(cat.id)"
              @change="setCategoryDefault(cat.id, $event.target.checked)"
            />
            <span class="fancy-check" aria-hidden="true">
              <svg viewBox="0 0 64 64">
                <path
                  d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
                  pathLength="575.0541381835938"
                  class="fancy-path"
                ></path>
              </svg>
            </span>
            <span>{{ cat.default_enabled ? '开' : '关' }}</span>
          </label>
          <div class="dropdown" :class="{ open: openForceMenu === cat.id }">
            <button type="button" class="ghost-btn" @click="toggleForceMenu(cat.id)">
              <span>{{ forcedLabel(cat.forced) }}</span>
              <span class="caret">▾</span>
            </button>
            <div class="dropdown-menu" v-if="openForceMenu === cat.id">
              <button type="button" @click="setForced(cat.id, null)">不强制</button>
              <button type="button" @click="setForced(cat.id, true)">强制启用</button>
              <button type="button" @click="setForced(cat.id, false)">强制禁用</button>
            </div>
          </div>
          <button type="button" class="link danger" @click="removeCategory(cat.id)">删除</button>
        </div>
      </div>
    </section>

    <section class="panel grid-2">
      <div>
        <div class="panel-title">
          <h2>模型禁用</h2>
        </div>
        <div class="toggle-grid">
          <label v-for="model in defaults.models" :key="model" class="toggle-row">
            <input type="checkbox" :checked="isModelDisabled(model)" @change="toggleModel(model)" />
            <span class="fancy-check" aria-hidden="true">
              <svg viewBox="0 0 64 64">
                <path
                  d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
                  pathLength="575.0541381835938"
                  class="fancy-path"
                ></path>
              </svg>
            </span>
            <span>{{ model }}</span>
          </label>
        </div>
      </div>
      <div>
        <div class="panel-title">
          <h2>前端禁用项</h2>
        </div>
        <div class="toggle-grid">
          <label v-for="key in defaults.ui_block_keys" :key="key" class="toggle-row">
            <input
              type="checkbox"
              :checked="!!form.config.ui_blocks[key]"
              @change="toggleUiBlock(key, $event)"
            />
            <span class="fancy-check" aria-hidden="true">
              <svg viewBox="0 0 64 64">
                <path
                  d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
                  pathLength="575.0541381835938"
                  class="fancy-path"
                ></path>
              </svg>
            </span>
            <span>{{ uiBlockLabel(key) }}</span>
          </label>
        </div>
      </div>
    </section>

    <section class="panel">
      <div class="panel-title">
        <h2>MCP 服务配置（统一工具扩展）</h2>
        <div class="header-actions">
          <button type="button" class="ghost-btn" :disabled="mcpLoading" @click="fetchMcpServers">
            {{ mcpLoading ? '刷新中...' : '刷新' }}
          </button>
          <button type="button" class="ghost-btn" :disabled="mcpSyncing" @click="syncAllMcpServers">
            {{ mcpSyncing ? '同步中...' : '同步全部工具' }}
          </button>
          <button type="button" @click="addMcpServer">新增 MCP 服务</button>
        </div>
      </div>
      <div v-if="!mcpServers.length" class="muted">暂无 MCP 服务，点击“新增 MCP 服务”开始配置。</div>
      <div class="mcp-list" v-else>
        <div class="mcp-item" v-for="(server, idx) in mcpServers" :key="server.id || `new-${idx}`">
          <div class="mcp-grid">
            <label>
              <span>ID</span>
              <input v-model="server.id" placeholder="如 demo_stdio" />
            </label>
            <label>
              <span>名称</span>
              <input v-model="server.name" placeholder="显示名称（可选）" />
            </label>
            <label>
              <span>Transport</span>
              <select v-model="server.transport">
                <option value="stdio">stdio</option>
                <option value="streamable_http">streamable_http</option>
              </select>
            </label>
            <label>
              <span>启用</span>
              <select v-model="server.enabledText">
                <option value="true">true</option>
                <option value="false">false</option>
              </select>
            </label>
            <label v-if="server.transport === 'stdio'" class="wide">
              <span>command</span>
              <input v-model="server.command" placeholder="如 python3" />
            </label>
            <label v-if="server.transport === 'stdio'" class="wide">
              <span>args（每行一个，留空=无）</span>
              <textarea
                v-model="server.argsText"
                rows="3"
                placeholder="如\n/abs/path/mcp_server.py"
              ></textarea>
            </label>
            <label v-if="server.transport === 'stdio'" class="wide">
              <span>cwd（可选）</span>
              <input v-model="server.cwd" placeholder="工作目录" />
            </label>
            <label v-if="server.transport === 'streamable_http'" class="wide">
              <span>url</span>
              <input v-model="server.url" placeholder="如 http://127.0.0.1:8000/mcp" />
            </label>
            <label class="wide">
              <span>headers（JSON，可选）</span>
              <textarea
                v-model="server.headersText"
                rows="2"
                placeholder='如 {"Authorization":"Bearer xxx"}'
              ></textarea>
            </label>
            <label class="wide">
              <span>env（JSON，可选，stdio 有效）</span>
              <textarea v-model="server.envText" rows="2" placeholder='如 {"PYTHONUNBUFFERED":"1"}'></textarea>
            </label>
            <label>
              <span>timeout(s)</span>
              <input v-model="server.timeoutText" placeholder="25" />
            </label>
            <label class="wide">
              <span>include_tools（逗号分隔，可选）</span>
              <input v-model="server.includeToolsText" placeholder="如 echo,read_db" />
            </label>
            <label class="wide">
              <span>exclude_tools（逗号分隔，可选）</span>
              <input v-model="server.excludeToolsText" placeholder="如 dangerous_tool" />
            </label>
            <label class="wide">
              <span>说明（可选）</span>
              <input v-model="server.description" placeholder="备注说明" />
            </label>
          </div>
          <div class="mcp-meta">
            <span>最近同步：{{ server.tools_cache_updated_at || '未同步' }}</span>
            <span v-if="server.last_error" class="error-text">错误：{{ server.last_error }}</span>
            <span>缓存工具数：{{ server.tools_cache_count }}</span>
          </div>
          <div class="mcp-actions">
            <button type="button" class="primary" @click="saveMcpServer(server)">保存</button>
            <button type="button" class="ghost-btn" @click="syncMcpServer(server.id)">同步工具</button>
            <button type="button" class="link danger" @click="deleteMcpServer(server.id, idx)">删除</button>
          </div>
        </div>
      </div>
    </section>

    <section class="panel">
      <div class="panel-title">
        <h2>已删除分类</h2>
      </div>
      <div class="chips">
        <span v-if="!form.config.remove_categories.length" class="muted">无</span>
        <span v-for="cid in form.config.remove_categories" :key="cid" class="chip">
          {{ cid }}
          <button type="button" @click="undoRemove(cid)">×</button>
        </span>
      </div>
    </section>

    <section class="panel muted-info">
      <p>说明：</p>
      <ul>
        <li>优先级：用户 &gt; 邀请码 &gt; 角色 &gt; 全局。</li>
        <li>分类“强制状态”会覆盖用户侧的工具开关，并在前端提示“被管理员强制”。</li>
        <li>UI 禁用项会在用户操作时弹出右上角提示。</li>
      </ul>
    </section>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, ref, onMounted, onBeforeUnmount, watch } from 'vue';
import { useSecondaryPass } from './useSecondaryPass';

type TargetType = 'global' | 'role' | 'user' | 'invite';

interface RawPolicy {
  updated_at?: string;
  global?: any;
  roles?: Record<string, any>;
  users?: Record<string, any>;
  invites?: Record<string, any>;
}

interface CategoryFormItem {
  id: string;
  label: string;
  tools: string[];
  default_enabled: boolean;
  forced: boolean | null;
}

interface MCPServerFormItem {
  id: string;
  name: string;
  description: string;
  transport: 'stdio' | 'streamable_http';
  enabledText: 'true' | 'false';
  command: string;
  argsText: string;
  cwd: string;
  url: string;
  headersText: string;
  envText: string;
  timeoutText: string;
  includeToolsText: string;
  excludeToolsText: string;
  tools_cache_updated_at: string;
  last_error: string;
  tools_cache_count: number;
}

const defaults = reactive({
  categories: {} as Record<string, any>,
  models: [] as string[],
  ui_block_keys: [] as string[]
});

const {
  verified: secondaryVerified,
  loading: secondaryLoading,
  error: secondaryError,
  check: checkSecondary,
  verify: verifySecondary
} = useSecondaryPass();
const secondaryPassword = ref('');

const form = reactive({
  target_type: 'global' as TargetType,
  target_value: '',
  config: {
    category_overrides: {} as Record<string, any>,
    remove_categories: [] as string[],
    forced_category_states: {} as Record<string, boolean>,
    disabled_models: [] as string[],
    ui_blocks: {} as Record<string, boolean>
  }
});

const lastUpdated = ref<string | null>(null);
const saving = ref(false);
const policyCache = ref<RawPolicy | null>(null);
const mcpServers = ref<MCPServerFormItem[]>([]);
const mcpLoading = ref(false);
const mcpSyncing = ref(false);

const targetPlaceholder = computed(() => {
  if (form.target_type === 'role') return '如：admin / user';
  if (form.target_type === 'invite') return '邀请码';
  if (form.target_type === 'user') return '用户名（小写）';
  return '';
});

const targetOptions = [
  { value: 'global', label: '全局' },
  { value: 'role', label: '按角色' },
  { value: 'user', label: '指定用户' },
  { value: 'invite', label: '邀请码' }
] as const;

const targetTypeLabel = computed(
  () => targetOptions.find((o) => o.value === form.target_type)?.label || '全局'
);
const targetMenuOpen = ref(false);
const openForceMenu = ref<string | null>(null);
const openToolMenu = ref<string | null>(null);
const toolSearch = ref('');

const banner = reactive({ message: '', type: 'info' as 'info' | 'success' | 'error' });

const toolOptionsSet = computed<Set<string>>(() => {
  const set = new Set<string>();
  const addList = (list: any) => {
    if (!Array.isArray(list)) return;
    list.forEach((t) => {
      if (typeof t === 'string' && t.trim()) set.add(t.trim());
    });
  };
  Object.values(defaults.categories || {}).forEach((cat: any) => addList(cat?.tools));
  categoryList.value.forEach((cat) => addList(cat.tools));
  return set;
});

const toolOptions = computed(() => Array.from(toolOptionsSet.value).sort());

const toolAssignments = computed<Record<string, string[]>>(() => {
  const map: Record<string, string[]> = {};
  categoryList.value.forEach((cat) => {
    (cat.tools || []).forEach((tool) => {
      if (!map[tool]) map[tool] = [];
      map[tool].push(cat.id);
    });
  });
  return map;
});

const optionsForCategory = (cat: CategoryFormItem) => {
  return toolOptions.value.filter((tool) => {
    const owners = toolAssignments.value[tool] || [];
    if (!owners.length) return true;
    return owners.length === 1 && owners[0] === cat.id;
  });
};

const filteredToolOptions = (cat: CategoryFormItem) => {
  const q = toolSearch.value.trim().toLowerCase();
  const base = optionsForCategory(cat);
  if (!q) return base;
  return base.filter((item) => item.toLowerCase().includes(q));
};

const toggleTargetMenu = () => {
  targetMenuOpen.value = !targetMenuOpen.value;
};

const pickTargetType = (value: TargetType) => {
  form.target_type = value;
  if (value === 'global') form.target_value = '';
  targetMenuOpen.value = false;
  openForceMenu.value = null;
  openToolMenu.value = null;
  applyScopeConfig();
};

const toggleForceMenu = (id: string) => {
  openForceMenu.value = openForceMenu.value === id ? null : id;
};

const forcedLabel = (value: boolean | null) => {
  if (value === true) return '强制启用';
  if (value === false) return '强制禁用';
  return '不强制';
};

const setForced = (id: string, value: boolean | null) => {
  const map = { ...form.config.forced_category_states };
  if (value === null) {
    delete map[id];
  } else {
    map[id] = value;
  }
  form.config.forced_category_states = map;
  openForceMenu.value = null;
};

const toggleToolMenu = (id: string) => {
  openToolMenu.value = openToolMenu.value === id ? null : id;
  toolSearch.value = '';
};

const toggleToolInCategory = (cat: CategoryFormItem, tool: string) => {
  const owners = toolAssignments.value[tool] || [];
  const conflict = owners.find((id) => id !== cat.id);
  if (conflict) {
    banner.message = `工具 ${tool} 已在分类 ${conflict} 中，请先移除后再分配`;
    banner.type = 'error';
    return;
  }
  const set = new Set(cat.tools || []);
  if (set.has(tool)) {
    set.delete(tool);
  } else {
    set.add(tool);
  }
  cat.tools = Array.from(set);
};

const addCustomTool = (cat: CategoryFormItem) => {
  const val = toolSearch.value.trim();
  if (!val) return;
  const owners = toolAssignments.value[val] || [];
  const conflict = owners.find((id) => id !== cat.id);
  if (conflict) {
    banner.message = `工具 ${val} 已在分类 ${conflict} 中，请先移除后再分配`;
    banner.type = 'error';
    return;
  }
  toggleToolInCategory(cat, val);
  toolSearch.value = '';
};

const handleDocClick = (event: MouseEvent) => {
  const target = event.target as HTMLElement | null;
  if (!target) return;
  if (!target.closest('.tool-select')) {
    openToolMenu.value = null;
  }
};

const categoryList = computed<CategoryFormItem[]>(() => {
  const map = form.config.category_overrides || {};
  return Object.keys(map).map((id) => ({
    id,
    label: map[id]?.label || id,
    tools: Array.isArray(map[id]?.tools) ? [...map[id].tools] : [],
    default_enabled: map[id]?.default_enabled !== false,
    forced: form.config.forced_category_states[id] ?? null
  }));
});

const getCategoryDefault = (id: string): boolean => {
  const map = form.config.category_overrides || {};
  if (map[id] && typeof map[id].default_enabled !== 'undefined') {
    return !!map[id].default_enabled;
  }
  const base = defaults.categories?.[id];
  return base ? !!base.default_enabled : true;
};

const setCategoryDefault = (id: string, enabled: boolean) => {
  const current = form.config.category_overrides || {};
  const base = current[id] || defaults.categories?.[id] || { label: id, tools: [] };
  form.config.category_overrides = {
    ...current,
    [id]: { ...base, default_enabled: enabled }
  };
};

function uiBlockLabel(key: string) {
  const map: Record<string, string> = {
    collapse_workspace: '折叠工作区',
    block_file_manager: '禁止文件管理器',
    block_personal_space: '禁止个人空间',
    block_upload: '禁止上传',
    block_conversation_review: '禁止对话引用',
    block_tool_toggle: '禁止工具禁用',
    block_realtime_terminal: '禁止实时终端',
    block_focus_panel: '禁止聚焦面板',
    block_token_panel: '禁止用量统计',
    block_compress_conversation: '禁止压缩对话',
    block_virtual_monitor: '禁止虚拟显示器'
  };
  return map[key] || key;
}

function toggleUiBlock(key: string, event: Event) {
  event?.stopPropagation?.();
  form.config.ui_blocks = {
    ...form.config.ui_blocks,
    [key]: !form.config.ui_blocks[key]
  };
}

function isModelDisabled(key: string) {
  return (form.config.disabled_models || []).includes(key);
}

function toggleModel(key: string) {
  const set = new Set(form.config.disabled_models || []);
  if (set.has(key)) {
    set.delete(key);
  } else {
    set.add(key);
  }
  form.config.disabled_models = Array.from(set);
}

function addCategory() {
  const id = `custom_${Date.now().toString(36)}`;
  form.config.category_overrides[id] = {
    label: id,
    tools: [],
    default_enabled: true
  };
}

function removeCategory(id: string) {
  delete form.config.category_overrides[id];
  form.config.forced_category_states[id] && delete form.config.forced_category_states[id];
  if (!form.config.remove_categories.includes(id)) {
    form.config.remove_categories.push(id);
  }
}

function undoRemove(id: string) {
  form.config.remove_categories = form.config.remove_categories.filter((item) => item !== id);
}

function rebuildCategoryOverrides() {
  const map: Record<string, any> = {};
  categoryList.value.forEach((item) => {
    if (!item.id) return;
    map[item.id] = {
      label: item.label || item.id,
      tools: (item.tools || []).map((s) => (typeof s === 'string' ? s.trim() : '')).filter(Boolean),
      default_enabled: !!item.default_enabled
    };
    if (item.forced === true || item.forced === false) {
      form.config.forced_category_states[item.id] = item.forced;
    } else {
      delete form.config.forced_category_states[item.id];
    }
  });
  form.config.category_overrides = map;
}

function splitCsv(value: string): string[] {
  return (value || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);
}

function linesToList(value: string): string[] {
  return (value || '')
    .split('\n')
    .map((item) => item.trim())
    .filter(Boolean);
}

function parseJsonDict(label: string, raw: string): Record<string, string> {
  const text = (raw || '').trim();
  if (!text) return {};
  let parsed: any;
  try {
    parsed = JSON.parse(text);
  } catch (error: any) {
    throw new Error(`${label} 不是合法 JSON：${error?.message || error}`);
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`${label} 必须是 JSON 对象`);
  }
  const output: Record<string, string> = {};
  Object.entries(parsed).forEach(([k, v]) => {
    const key = String(k || '').trim();
    if (!key) return;
    output[key] = String(v ?? '');
  });
  return output;
}

function mapMcpServer(raw: any): MCPServerFormItem {
  const transport = raw?.transport === 'streamable_http' ? 'streamable_http' : 'stdio';
  const args = Array.isArray(raw?.args) ? raw.args.filter((x: any) => typeof x === 'string') : [];
  const toolsCache = Array.isArray(raw?.tools_cache) ? raw.tools_cache : [];
  return {
    id: String(raw?.id || ''),
    name: String(raw?.name || raw?.id || ''),
    description: String(raw?.description || ''),
    transport,
    enabledText: raw?.enabled === false ? 'false' : 'true',
    command: String(raw?.command || ''),
    argsText: args.join('\n'),
    cwd: String(raw?.cwd || ''),
    url: String(raw?.url || ''),
    headersText: raw?.headers && typeof raw.headers === 'object' ? JSON.stringify(raw.headers, null, 2) : '',
    envText: raw?.env && typeof raw.env === 'object' ? JSON.stringify(raw.env, null, 2) : '',
    timeoutText: String(raw?.timeout_seconds ?? 25),
    includeToolsText: Array.isArray(raw?.include_tools) ? raw.include_tools.join(',') : '',
    excludeToolsText: Array.isArray(raw?.exclude_tools) ? raw.exclude_tools.join(',') : '',
    tools_cache_updated_at: String(raw?.tools_cache_updated_at || ''),
    last_error: String(raw?.last_error || ''),
    tools_cache_count: toolsCache.length
  };
}

function normalizeMcpPayload(server: MCPServerFormItem) {
  const id = server.id.trim();
  if (!id) throw new Error('MCP 服务 ID 不能为空');
  return {
    id,
    name: server.name.trim() || id,
    description: server.description.trim(),
    transport: server.transport,
    enabled: server.enabledText === 'true',
    command: server.command.trim(),
    args: linesToList(server.argsText),
    cwd: server.cwd.trim(),
    url: server.url.trim(),
    headers: parseJsonDict('headers', server.headersText),
    env: parseJsonDict('env', server.envText),
    timeout_seconds: Number(server.timeoutText || 25),
    include_tools: splitCsv(server.includeToolsText),
    exclude_tools: splitCsv(server.excludeToolsText)
  };
}

async function fetchMcpServers() {
  if (!secondaryVerified.value) return;
  mcpLoading.value = true;
  try {
    const resp = await fetch('/api/admin/mcp-servers', { credentials: 'same-origin' });
    const data = await resp.json();
    if (!resp.ok || !data.success) {
      throw new Error(data.error || '加载 MCP 服务失败');
    }
    mcpServers.value = (data.data || []).map((item: any) => mapMcpServer(item));
  } catch (error: any) {
    banner.message = error?.message || '加载 MCP 服务失败';
    banner.type = 'error';
  } finally {
    mcpLoading.value = false;
  }
}

function addMcpServer() {
  mcpServers.value.unshift(
    mapMcpServer({
      id: '',
      name: '',
      transport: 'stdio',
      enabled: true,
      timeout_seconds: 25
    })
  );
}

async function saveMcpServer(server: MCPServerFormItem) {
  if (!secondaryVerified.value) return;
  try {
    const payload = normalizeMcpPayload(server);
    const resp = await fetch('/api/admin/mcp-servers', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    });
    const data = await resp.json();
    if (!resp.ok || !data.success) {
      throw new Error(data.error || '保存 MCP 服务失败');
    }
    banner.message = `MCP 服务 ${payload.id} 已保存`;
    banner.type = 'success';
    await syncMcpServer(payload.id, true);
  } catch (error: any) {
    banner.message = error?.message || '保存 MCP 服务失败';
    banner.type = 'error';
  }
}

async function deleteMcpServer(id: string, idx: number) {
  if (!secondaryVerified.value) return;
  const target = String(id || '').trim();
  if (!target) {
    mcpServers.value.splice(idx, 1);
    return;
  }
  try {
    const resp = await fetch(`/api/admin/mcp-servers?id=${encodeURIComponent(target)}`, {
      method: 'DELETE',
      credentials: 'same-origin'
    });
    const data = await resp.json();
    if (!resp.ok || !data.success) {
      throw new Error(data.error || '删除 MCP 服务失败');
    }
    banner.message = `已删除 MCP 服务 ${target}`;
    banner.type = 'success';
    await fetchMcpServers();
    await fetchDefaults();
  } catch (error: any) {
    banner.message = error?.message || '删除 MCP 服务失败';
    banner.type = 'error';
  }
}

async function syncMcpServer(id?: string, silent = false) {
  if (!secondaryVerified.value) return;
  const target = String(id || '').trim();
  if (typeof id !== 'undefined' && !target) {
    banner.message = '请先保存服务后再同步工具';
    banner.type = 'error';
    return;
  }
  if (!silent) mcpSyncing.value = true;
  try {
    const resp = await fetch('/api/admin/mcp-servers/sync', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(target ? { id: target } : {})
    });
    const data = await resp.json();
    if (!resp.ok || !data.success) {
      throw new Error(data.error || '同步 MCP 工具失败');
    }
    if (!silent) {
      banner.message = target ? `已同步 ${target}` : '已同步全部 MCP 服务';
      banner.type = 'success';
    }
    await fetchMcpServers();
    await fetchDefaults();
  } catch (error: any) {
    banner.message = error?.message || '同步 MCP 工具失败';
    banner.type = 'error';
  } finally {
    if (!silent) mcpSyncing.value = false;
  }
}

async function syncAllMcpServers() {
  await syncMcpServer();
}

async function fetchDefaults() {
  if (!secondaryVerified.value) return;
  const resp = await fetch('/api/admin/policy', { credentials: 'same-origin' });
  const data = await resp.json();
  if (!resp.ok || !data.success) {
    throw new Error(data.error || '加载默认配置失败');
  }
  defaults.categories = data.defaults?.categories || {};
  defaults.models = data.defaults?.models || [];
  defaults.ui_block_keys = data.defaults?.ui_block_keys || [];
  policyCache.value = data.data;
  lastUpdated.value = data.data?.updated_at || null;
  applyScopeConfig();
}

function scopeConfig(): any {
  const p = policyCache.value;
  if (!p) return null;
  if (form.target_type === 'global') return p.global || {};
  if (form.target_type === 'role') return (p.roles || {})[form.target_value] || {};
  if (form.target_type === 'user') return (p.users || {})[form.target_value] || {};
  if (form.target_type === 'invite') return (p.invites || {})[form.target_value] || {};
  return null;
}

function applyScopeConfig() {
  const cfg = scopeConfig() || {};
  const base = JSON.parse(JSON.stringify(defaults.categories || {}));
  const overrides = cfg.category_overrides || {};
  const removed = new Set(cfg.remove_categories || []);

  Object.keys(overrides).forEach((key) => {
    base[key] = overrides[key];
  });
  removed.forEach((id: string) => {
    delete base[id];
  });

  form.config.category_overrides = base;
  form.config.remove_categories = [...removed];
  form.config.forced_category_states = { ...(cfg.forced_category_states || {}) };
  form.config.disabled_models = [...(cfg.disabled_models || [])];
  form.config.ui_blocks = { ...(cfg.ui_blocks || {}) };
  openForceMenu.value = null;
  openToolMenu.value = null;
}

async function loadScope() {
  if (!secondaryVerified.value) return;
  if (!policyCache.value) {
    await fetchDefaults();
    return;
  }
  applyScopeConfig();
}

async function savePolicy() {
  if (!secondaryVerified.value) return;
  rebuildCategoryOverrides();
  saving.value = true;
  try {
    const resp = await fetch('/api/admin/policy', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        target_type: form.target_type,
        target_value: form.target_type === 'global' ? 'global' : form.target_value,
        config: form.config
      })
    });
    const result = await resp.json();
    if (!resp.ok || !result.success) {
      throw new Error(result.error || '保存失败');
    }
    policyCache.value = result.data;
    lastUpdated.value = result.data?.updated_at || null;
    banner.message = '保存成功';
    banner.type = 'success';
  } catch (error: any) {
    banner.message = error?.message || '保存失败';
    banner.type = 'error';
  } finally {
    saving.value = false;
  }
}

const handleVerifySecondary = async () => {
  await verifySecondary(secondaryPassword.value);
  if (secondaryVerified.value) {
    secondaryPassword.value = '';
    Promise.all([fetchDefaults(), fetchMcpServers()]).catch((err) => {
      console.error(err);
      banner.message = err?.message || '加载策略失败';
      banner.type = 'error';
    });
  }
};

onMounted(async () => {
  document.addEventListener('click', handleDocClick);
  await checkSecondary();
  if (secondaryVerified.value) {
    Promise.all([fetchDefaults(), fetchMcpServers()]).catch((err) => {
      console.error(err);
      banner.message = err?.message || '加载策略失败';
      banner.type = 'error';
    });
  }
});

watch(secondaryVerified, (val) => {
  if (val) {
    Promise.all([fetchDefaults(), fetchMcpServers()]).catch((err) => {
      console.error(err);
      banner.message = err?.message || '加载策略失败';
      banner.type = 'error';
    });
  }
});

onBeforeUnmount(() => {
  document.removeEventListener('click', handleDocClick);
});
</script>

<style scoped>
:global(body) {
  margin: 0;
  background: #f7f3ea;
  font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, 'Times New Roman', serif;
  color: #2a2013;
}

:global(#admin-policy-app) {
  min-height: 100vh;
  padding: 24px 32px 48px;
  box-sizing: border-box;
}

.policy-page {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.policy-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.header-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}

.dropdown {
  position: relative;
}

.ghost-btn {
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: rgba(255, 255, 255, 0.9);
  color: #2a2013;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.ghost-btn .caret {
  font-size: 12px;
  opacity: 0.7;
}

.dropdown-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 160px;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
}

.dropdown-menu button {
  border: none;
  background: transparent;
  padding: 10px 12px;
  text-align: left;
  width: 100%;
  color: #2a2013;
}

.dropdown-menu button:hover {
  background: rgba(0, 0, 0, 0.04);
}

.text-field input {
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
}

.banner {
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: 1px solid rgba(0, 0, 0, 0.08);
}

.banner.success {
  background: rgba(73, 160, 120, 0.12);
  border-color: rgba(73, 160, 120, 0.35);
}

.banner.error {
  background: rgba(189, 93, 58, 0.12);
  border-color: rgba(189, 93, 58, 0.35);
}

.banner-close {
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}

.panel {
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(118, 103, 84, 0.2);
  border-radius: 16px;
  padding: 16px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
}

.mcp-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.mcp-item {
  border: 1px solid rgba(118, 103, 84, 0.2);
  border-radius: 14px;
  padding: 12px;
  background: rgba(255, 255, 255, 0.92);
}

.mcp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 10px;
}

.mcp-grid label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: #5b4d3b;
  font-size: 13px;
}

.mcp-grid label.wide {
  grid-column: 1 / -1;
}

.mcp-grid textarea,
.mcp-grid input,
.mcp-grid select {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
}

.mcp-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
  color: #8a7a62;
  font-size: 12px;
}

.mcp-actions {
  margin-top: 8px;
  display: flex;
  gap: 8px;
}

.error-text {
  color: #b05b3c;
}

.panel-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 12px;
}

.category-table {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.category-row {
  display: grid;
  grid-template-columns: 1.2fr 1.2fr 2fr 0.8fr 1fr 0.8fr;
  gap: 8px;
  align-items: center;
}

.category-head {
  font-weight: 600;
  color: #5b4d3b;
}

.category-row input,
.category-row select {
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
}

.id-input {
  font-family:
    'SFMono-Regular', ui-monospace, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
    monospace;
}

.checkbox-cell {
  display: flex;
  justify-content: center;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
}

.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px;
}

.toggle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
}

.check-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-radius: 14px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: rgba(255, 255, 255, 0.92);
  cursor: pointer;
  transition: all 0.2s ease;
}

.check-pill.compact {
  padding: 8px 10px;
}

.check-pill .pill-knob {
  width: 34px;
  height: 18px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.12);
  position: relative;
  transition: all 0.2s ease;
}

.check-pill .pill-knob::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  transition: all 0.2s ease;
}

.check-pill.on {
  border-color: rgba(73, 160, 120, 0.45);
  box-shadow: 0 6px 18px rgba(73, 160, 120, 0.18);
}

.check-pill.on .pill-knob {
  background: linear-gradient(90deg, #49a078, #4fb28a);
}

.check-pill.on .pill-knob::after {
  transform: translateX(16px);
}

.check-pill .pill-label {
  font-weight: 600;
  color: #2a2013;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  background: rgba(118, 103, 84, 0.12);
  border: 1px solid rgba(118, 103, 84, 0.3);
  border-radius: 999px;
  padding: 6px 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.muted {
  color: #8a7a62;
}

.muted-info {
  color: #6a5d4c;
  background: rgba(247, 243, 234, 0.9);
}

.link {
  background: transparent;
  border: 1px solid rgba(176, 91, 60, 0.28);
  color: #5b4d3b;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 10px;
  transition:
    background 0.15s ease,
    border-color 0.15s ease;
}

.link.danger {
  color: #b05b3c;
  border-color: rgba(176, 91, 60, 0.4);
  background: rgba(176, 91, 60, 0.06);
}

.link.danger:hover {
  background: rgba(176, 91, 60, 0.12);
  border-color: rgba(176, 91, 60, 0.55);
}

button {
  padding: 8px 12px;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
  cursor: pointer;
}

button.primary {
  background: linear-gradient(90deg, #4b2e14, #8b5d3b);
  color: #fff;
  border: none;
}

button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* 复用个人空间的勾选样式 */
.toggle-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid var(--theme-control-border);
  background: var(--theme-surface-muted);
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease,
    background 0.2s ease;
}

.toggle-row.compact {
  padding: 10px 12px;
}

.toggle-row:hover {
  border-color: var(--theme-control-border-strong);
  background: var(--theme-surface-soft);
}

.toggle-row input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.toggle-row .fancy-check {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: none;
  background: transparent;
}

.toggle-row .fancy-check svg {
  width: 22px;
  height: 22px;
  overflow: visible;
}

.fancy-path {
  fill: none;
  stroke: var(--claude-text-secondary);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition:
    stroke-dasharray 0.5s ease,
    stroke-dashoffset 0.5s ease,
    stroke 0.2s ease;
  stroke-dasharray: 241 9999999;
  stroke-dashoffset: 0;
}

.toggle-row input:checked + .fancy-check .fancy-path {
  stroke: var(--claude-accent);
  stroke-dasharray: 70.5096664428711 9999999;
  stroke-dashoffset: -262.2723388671875;
}

.toggle-row span {
  color: var(--claude-text);
  font-weight: 600;
}

.tool-select {
  position: relative;
}

.tool-select-trigger {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 10px;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
  cursor: pointer;
  min-height: 40px;
}

.tool-select.open .tool-select-trigger {
  border-color: rgba(118, 103, 84, 0.4);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
}

.tool-badges {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

.tool-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(118, 103, 84, 0.12);
  color: #4b3d2f;
  border: 1px solid rgba(118, 103, 84, 0.2);
  font-size: 13px;
}

.tool-select-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  min-width: 260px;
  max-height: 240px;
  background: #fffaf4;
  border: 1px solid rgba(118, 103, 84, 0.2);
  border-radius: 12px;
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.12);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow: hidden;
}

.tool-select-search input {
  width: 100%;
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  background: #fff;
}

.tool-select-options {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  max-height: 140px;
  overflow: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.tool-select-options::-webkit-scrollbar {
  display: none;
}

.tool-select-options label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.06);
  cursor: pointer;
}

.tool-select-options input {
  position: static;
  opacity: 1;
}

.link.small {
  font-size: 13px;
  align-self: flex-start;
  padding: 6px 8px;
}

.tiny {
  font-size: 12px;
}

.secondary-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 12, 5, 0.35);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}

.secondary-card {
  width: 360px;
  max-width: 90vw;
  background: #f6ecda;
  border: 1px solid rgba(118, 103, 84, 0.35);
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.16);
}

.secondary-card h3 {
  margin: 0 0 8px;
}

.secondary-card p {
  margin: 0 0 12px;
  color: #5b4b35;
}

.secondary-input {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(44, 32, 19, 0.2);
  margin-bottom: 12px;
  font-size: 15px;
}

.secondary-actions {
  display: flex;
  gap: 10px;
  align-items: center;
}

.secondary-actions .ghost-btn {
  background: transparent;
  border: 1px dashed rgba(44, 32, 19, 0.35);
  padding: 10px 14px;
  border-radius: 12px;
  cursor: pointer;
}

.secondary-error {
  color: #b5473d;
  margin-top: 10px;
}
</style>
