/** @typedef {'auto' | 'approve' | 'smart_approve' | 'chat'} GooseMode */
/** @typedef {'readwrite' | 'readonly'} WorkspaceAccess */
/** @typedef {'allow' | 'deny'} NetworkEgress */

export const POLICY_CATALOG = [
  {
    key: 'goose_mode',
    label: 'TKMind 模式',
    description:
      'H5 终端用户不在聊天里点「允许/拒绝」；可用工具由「能力权限」决定。chat 不调用工具，auto 在授权范围内自动执行。',
    type: 'select',
    options: [
      { value: 'chat', label: '仅聊天 (chat)' },
      { value: 'auto', label: '自动执行 (auto)' },
    ],
    defaultValue: 'chat',
    category: 'goose',
    risk: 'high',
  },
  {
    key: 'workspace_access',
    label: '工作区访问',
    description: 'readonly 时禁止 shell 与文件写入，仅允许浏览与分析',
    type: 'select',
    options: [
      { value: 'readwrite', label: '读写' },
      { value: 'readonly', label: '只读' },
    ],
    defaultValue: 'readwrite',
    category: 'workspace',
    risk: 'medium',
  },
  {
    key: 'network_egress',
    label: '网络出站',
    description: 'deny 时禁用 shell、电脑控制、沙箱脚本等可能访问网络的扩展',
    type: 'select',
    options: [
      { value: 'deny', label: '禁止（推荐）' },
      { value: 'allow', label: '允许（需同时开启对应能力）' },
    ],
    defaultValue: 'deny',
    category: 'network',
    risk: 'high',
  },
  {
    key: 'api_lockdown',
    label: 'API 代理锁定',
    description: '开启后仅允许聊天相关 API，拦截改模式、加扩展、写配置等危险请求',
    type: 'boolean',
    defaultValue: true,
    category: 'proxy',
    risk: 'medium',
  },
  {
    key: 'code_delegate_executor',
    label: '代码委托执行器',
    description:
      '控制 Goose 在多文件编码、修复与重构任务中优先委托给谁。auto 由 Goose 结合当前可用扩展自行判断；aider / openhands 则优先使用指定执行器。',
    type: 'select',
    options: [
      { value: 'auto', label: '自动选择 (auto)' },
      { value: 'aider', label: '优先 Aider' },
      { value: 'openhands', label: '优先 OpenHands' },
    ],
    defaultValue: 'auto',
    category: 'routing',
    risk: 'medium',
  },
  {
    key: 'code_task_routing',
    label: '代码任务路由策略',
    description:
      '定义代码任务如何在 Aider 与 OpenHands 之间分流。balanced 由 Goose 按任务复杂度判断；split 建议小改动走 Aider、复杂任务走 OpenHands；force_* 则强制优先单一路径。',
    type: 'select',
    options: [
      { value: 'balanced', label: '平衡路由 (balanced)' },
      { value: 'split', label: '小改动 Aider，复杂任务 OpenHands' },
      { value: 'force_aider', label: '尽量统一走 Aider' },
      { value: 'force_openhands', label: '尽量统一走 OpenHands' },
    ],
    defaultValue: 'balanced',
    category: 'routing',
    risk: 'medium',
  },
];

export const DEFAULT_USER_POLICIES = Object.fromEntries(
  POLICY_CATALOG.map((item) => [item.key, item.defaultValue]),
);

const POLICY_KEYS = new Set(POLICY_CATALOG.map((item) => item.key));
const GOOSE_MODES = new Set(['auto', 'approve', 'smart_approve', 'chat']);
const WORKSPACE_ACCESS = new Set(['readwrite', 'readonly']);
const NETWORK_EGRESS = new Set(['allow', 'deny']);
const CODE_DELEGATE_EXECUTORS = new Set(['auto', 'aider', 'openhands']);
const CODE_TASK_ROUTINGS = new Set(['balanced', 'split', 'force_aider', 'force_openhands']);

export function policyKeys() {
  return [...POLICY_KEYS];
}

export function isValidPolicyKey(key) {
  return POLICY_KEYS.has(key);
}

export function normalizePolicyPatch(patch) {
  const normalized = {};
  for (const [key, raw] of Object.entries(patch ?? {})) {
    if (!isValidPolicyKey(key)) continue;
    const def = POLICY_CATALOG.find((item) => item.key === key);
    if (!def) continue;
    if (def.type === 'boolean') {
      normalized[key] = raw === true || raw === 'true' || raw === 1 || raw === '1';
      continue;
    }
    const value = String(raw ?? '').trim();
    if (def.key === 'goose_mode' && GOOSE_MODES.has(value)) normalized[key] = value;
    if (def.key === 'workspace_access' && WORKSPACE_ACCESS.has(value)) normalized[key] = value;
    if (def.key === 'network_egress' && NETWORK_EGRESS.has(value)) normalized[key] = value;
    if (def.key === 'code_delegate_executor' && CODE_DELEGATE_EXECUTORS.has(value)) {
      normalized[key] = value;
    }
    if (def.key === 'code_task_routing' && CODE_TASK_ROUTINGS.has(value)) {
      normalized[key] = value;
    }
  }
  return normalized;
}

export function resolvePolicies(rolePolicies, userOverrides) {
  const resolved = { ...DEFAULT_USER_POLICIES };
  for (const [key, value] of Object.entries(rolePolicies ?? {})) {
    if (isValidPolicyKey(key)) resolved[key] = value;
  }
  for (const [key, value] of Object.entries(userOverrides ?? {})) {
    if (isValidPolicyKey(key)) resolved[key] = value;
  }
  if (resolved.goose_mode === 'approve' || resolved.goose_mode === 'smart_approve') {
    resolved.goose_mode = 'auto';
  }
  return resolved;
}

function hasExecutableTools(capabilities) {
  return Boolean(
    capabilities?.static_publish
      || capabilities?.shell
      || capabilities?.filesystem
      || capabilities?.code_browse
      || capabilities?.subagent
      || capabilities?.code_sandbox
      || capabilities?.computer
      || capabilities?.charts
      || capabilities?.aider
      || capabilities?.openhands
      || capabilities?.apps
      || capabilities?.todo
      || capabilities?.skills
      || capabilities?.chat_recall,
  );
}

/**
 * Goose mode for H5 agent sessions. Tool allow/deny is enforced via capability grants,
 * not end-user confirmation dialogs.
 */
export function resolveAgentGooseMode(capabilities, policies) {
  const requested = policies?.goose_mode ?? DEFAULT_USER_POLICIES.goose_mode;
  if (!hasExecutableTools(capabilities)) {
    return 'chat';
  }
  if (requested === 'chat' || requested === 'approve' || requested === 'smart_approve') {
    return 'auto';
  }
  return 'auto';
}

/**
 * Apply security policies on top of capability flags before building extension overrides.
 */
export function applyPoliciesToCapabilities(capabilities, policies) {
  const effective = { ...capabilities };
  if (policies.workspace_access === 'readonly') {
    effective.shell = false;
    effective.filesystem = false;
    effective.static_publish = false;
    effective.aider = false;
    effective.openhands = false;
    effective.code_sandbox = false;
    effective.image_read = false;
  }
  if (policies.network_egress === 'deny') {
    // static_publish 用户的 shell 仅能在本工作区内执行本地命令，不依赖外网出站
    if (!effective.static_publish) {
      effective.shell = false;
    }
    effective.computer = false;
    effective.code_sandbox = false;
  }
  return effective;
}

const USER_API_ALLOWLIST = [
  { method: 'GET', pattern: /^\/status$/ },
  { method: 'POST', pattern: /^\/agent\/start$/ },
  { method: 'POST', pattern: /^\/agent\/resume$/ },
  { method: 'GET', pattern: /^\/sessions$/ },
  { method: 'GET', pattern: /^\/sessions\/[^/]+$/ },
  { method: 'DELETE', pattern: /^\/sessions\/[^/]+$/ },
  { method: 'GET', pattern: /^\/sessions\/[^/]+\/events$/ },
  { method: 'POST', pattern: /^\/sessions\/[^/]+\/reply$/ },
  { method: 'POST', pattern: /^\/sessions\/[^/]+\/cancel$/ },
  { method: 'POST', pattern: /^\/action-required\/tool-confirmation$/ },
  { method: 'POST', pattern: /^\/agent\/update_provider$/ },
  { method: 'POST', pattern: /^\/config\/read$/ },
  { method: 'POST', pattern: /^\/agent\/harness_bootstrap$/ },
  { method: 'POST', pattern: /^\/agent\/harness_remember$/ },
];

export function isNativeH5ApiPath(pathname) {
  const path = String(pathname ?? '');
  return path.startsWith('/mindspace/') || path.startsWith('/plaza/');
}

/**
 * @returns {{ allowed: boolean, reason?: string }}
 */
export function evaluateProxyRequest(method, pathname, policies, { unrestricted = false } = {}) {
  if (unrestricted) return { allowed: true };
  if (!policies.api_lockdown) return { allowed: true };
  if (isNativeH5ApiPath(pathname)) {
    return {
      allowed: false,
      reason: `H5 本地接口不应走 Agent 代理：${method.toUpperCase()} ${pathname}`,
    };
  }

  const upper = method.toUpperCase();
  const allowed = USER_API_ALLOWLIST.some(
    (rule) => rule.method === upper && rule.pattern.test(pathname),
  );
  if (allowed) return { allowed: true };

  return {
    allowed: false,
    reason: `策略已锁定 API：${upper} ${pathname}`,
  };
}
