import { getActivePreview } from "./preview";
import { normalizeRole } from "./agentPerms";

const PLATFORM = "";
export const AI = "/ai";
const SESSION_KEY = "ajz_session";

export type Session = {
  accessToken: string;
  agentKey: string;
  tenantId: number;
  userId: number;
  username?: string;
  phone?: string;
  usernameLoginDisabled?: boolean;
  displayName?: string;
  role?: string;
  orgUnitId?: number;
  status?: string; // pending | active
  expiresAt?: number;
  /** 超管打开某公司管理视图时的公司名（身份仍是超管） */
  tenantName?: string;
};

export function isPendingMembership(s: Session | null | undefined): boolean {
  if (!s) return false;
  const role = normalizeRole(s.role || "");
  // 平台超管无租户，绝不能当成「待加入」
  if (role === "超级管理员") return false;
  if (s.username === "ljk_admin") return false;
  if (role === "待加入" || s.status === "pending") return true;
  // status=active 且无租户：仍可能是待入驻注册用户
  if (!s.tenantId) return true;
  return false;
}

function roleFromAccessToken(token: string | undefined): string {
  if (!token || token.split(".").length < 2) return "";
  try {
    const b64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
    const pad = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
    const bin = atob(pad);
    const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));
    const json = JSON.parse(new TextDecoder().decode(bytes));
    return typeof json?.role === "string" ? json.role : "";
  } catch {
    return "";
  }
}

/** emit=false：仅清存储（如 loadSession 发现坏 token），不踢路由——避免毁掉 /#/preview */
export function clearSession(opts?: { emit?: boolean }) {
  try {
    localStorage.removeItem(SESSION_KEY);
  } catch {
    /* ignore */
  }
  if (opts?.emit === false) return;
  if (typeof window !== "undefined") {
    window.dispatchEvent(new CustomEvent("ajz:session-expired"));
  }
}

export function loadSession(): Session | null {
  try {
    const raw = localStorage.getItem(SESSION_KEY);
    if (!raw) return null;
    const s = JSON.parse(raw) as Session;
    if (!s?.accessToken || typeof s.accessToken !== "string" || s.accessToken.split(".").length !== 3) {
      clearSession({ emit: false });
      return null;
    }
    if (s.expiresAt && s.expiresAt * 1000 < Date.now() - 60_000) {
      clearSession({ emit: false });
      return null;
    }
    if (!s.role) {
      s.role = roleFromAccessToken(s.accessToken);
    }
    return s;
  } catch {
    clearSession({ emit: false });
    return null;
  }
}

export function saveSession(s: Session) {
  localStorage.setItem(SESSION_KEY, JSON.stringify(s));
}

function toSession(data: any): Session {
  const accessToken = data.access_token;
  const s: Session = {
    accessToken,
    agentKey: data.agent_key,
    tenantId: data.tenant_id || 0,
    userId: data.user_id,
    username: data.username,
    phone: data.phone || undefined,
    usernameLoginDisabled: !!data.username_login_disabled,
    displayName: data.display_name,
    role: data.role || roleFromAccessToken(accessToken),
    orgUnitId: data.org_unit_id || 0,
    status: data.status,
    expiresAt: data.expires_at,
    tenantName: data.tenant_name || undefined,
  };
  if (!s.accessToken) {
    throw new Error("登录响应缺少 access_token");
  }
  return s;
}

function networkError(err: unknown): Error {
  const msg = String((err as any)?.message || err);
  if (/failed to fetch|networkerror|load failed|network request failed/i.test(msg)) {
    return new Error("无法连接网关或前端代理，请确认服务已启动后刷新页面");
  }
  return err instanceof Error ? err : new Error(msg);
}

function authErrorMessage(data: any, status: number): string {
  const msg = String(data?.message || data?.msg || data?.error || "");
  // 登录/注册接口本身的 401 只表示账号密码错误，不能清掉会话事件循环
  if (status === 401 || /invalid token|malformed token|expired|missing bearer/i.test(msg)) {
    if (/invalid username or password|账号已停用|login failed/i.test(msg)) {
      return msg || "用户名或密码错误";
    }
    clearSession();
    return msg || "登录已失效，请重新登录";
  }
  return msg;
}

async function apiFetch(input: string, init?: RequestInit): Promise<Response> {
  try {
    return await fetch(input, init);
  } catch (e) {
    throw networkError(e);
  }
}

async function readJson(res: Response): Promise<any> {
  const text = await res.text();
  if (!text) return {};
  try {
    return JSON.parse(text);
  } catch {
    throw new Error(res.ok ? "响应不是 JSON" : `请求失败 HTTP ${res.status}`);
  }
}

export async function login(username: string, password: string): Promise<Session> {
  return loginWith({ username: username.trim(), password });
}

export async function loginWith(body: {
  username?: string;
  phone?: string;
  password?: string;
  sms_code?: string;
}): Promise<Session> {
  const res = await apiFetch(`${PLATFORM}/api/v1/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  if (!res.ok) {
    throw new Error(data.message || (res.status === 401 ? "登录失败" : "login failed"));
  }
  return toSession(data);
}

export async function sendLoginSMS(phone: string) {
  const res = await apiFetch(`${PLATFORM}/api/v1/auth/sms/send`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ phone: phone.trim(), purpose: "login" }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "send sms failed");
  return data as {
    ok: boolean;
    expires_in: number;
    retry_after?: number;
    message?: string;
    debug_code?: string;
  };
}

export async function register(username: string, password: string, displayName: string): Promise<Session> {
  const res = await apiFetch(`${PLATFORM}/api/v1/auth/register`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password, display_name: displayName }),
  });
  const data = await readJson(res);
  if (!res.ok) throw new Error(data.message || "register failed");
  return toSession(data);
}

export async function listLlmProviders() {
  const res = await apiFetch(`${AI}/api/v1/llm/providers`);
  const data = await readJson(res);
  if (!res.ok) throw new Error(JSON.stringify(data));
  return data as {
    default: string;
    providers: {
      id: string;
      label: string;
      configured: boolean;
      default_model: string;
      models: { id: string; label: string }[];
      supports_vision: boolean;
    }[];
  };
}

export async function generateDraft(
  prompt: string,
  excel?: File | null,
  images?: File[],
  storageMode: "schema_per_app" | "database_per_app" = "schema_per_app",
  llmProvider = "deepseek",
  llmModel = "",
  layoutFiles?: File[]
) {
  const fd = new FormData();
  fd.append("prompt", prompt);
  fd.append("storage_mode", storageMode);
  fd.append("llm_provider", llmProvider);
  if (llmModel) fd.append("llm_model", llmModel);
  if (excel) fd.append("excel", excel);
  for (const img of images || []) {
    fd.append("images", img);
  }
  for (const f of layoutFiles || []) {
    fd.append("layout_files", f);
  }
  const res = await apiFetch(`${AI}/api/v1/apps/generate`, { method: "POST", body: fd });
  const data = await readJson(res);
  if (!res.ok) throw new Error(JSON.stringify(data));
  return data as {
    draft: any;
    warnings: string[];
    confidence: number;
    require_confirm: boolean;
    llm_provider?: string;
    llm_model?: string;
    fidelity?: {
      skipped?: boolean;
      reason?: string;
      target?: number;
      max_rounds?: number;
      final_score?: number;
      passed?: boolean;
      rounds?: { round: number; score?: number; pass?: boolean; fails?: string[]; passes?: string[] }[];
    };
    generate_log?: {
      run_id?: string;
      started_at?: string;
      log_file?: string;
      lines?: { ts?: string; level?: string; message?: string; [k: string]: unknown }[];
    };
  };
}

function throwIfBad(res: Response, data: any, fallback: string): void {
  if (res.ok) return;
  const msg = authErrorMessage(data, res.status) || fallback || `请求失败 (HTTP ${res.status})`;
  throw new Error(msg);
}

export async function listApps(session: Session) {
  const res = await apiFetch(`${PLATFORM}/api/v1/apps`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "列出模块失败");
  return data as {
    scope?: string;
    items: {
      app_id: string;
      slug: string;
      name: string;
      status: string;
      status_label?: string;
      building?: boolean;
      schema_name?: string;
      page_count: number;
      entity_count: number;
      updated_at: string;
      created_at: string;
    }[];
  };
}

/** Z9e：为本租户已发布模块补齐 import/export（一键开启导入） */
export async function ensureAppsImport(session: Session, dryRun = false) {
  const q = dryRun ? "?dry_run=1" : "";
  const res = await apiFetch(`${PLATFORM}/api/v1/admin/apps/ensure-import${q}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "一键开启导入失败");
  return data as {
    scanned: number;
    updated: number;
    skipped: number;
    dry_run: boolean;
    updated_slugs?: string[];
    message?: string;
  };
}

export async function saveDraft(session: Session, slug: string, blueprint: any) {
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/draft`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${session.accessToken}`,
    },
    body: JSON.stringify({ blueprint }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "登记在建模块失败");
  return data;
}

export async function publish(
  session: Session,
  slug: string,
  blueprint: any,
  mode: "auto" | "add_pages" | "create" | "replace" | "merge" = "auto",
) {
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/publish`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${session.accessToken}`,
    },
    body: JSON.stringify({ blueprint, mode: mode === "merge" ? "add_pages" : mode }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "发布失败");
  return data;
}

export async function getBlueprint(session: Session | null | undefined, slug: string) {
  if (session?.accessToken) {
    const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/blueprint`, {
      headers: { Authorization: `Bearer ${session.accessToken}` },
      cache: "no-store",
    });
    const data = await readJson(res);
    throwIfBad(res, data, "blueprint failed");
    return data;
  }
  const res = await apiFetch(`${PLATFORM}/api/v1/public/apps/${slug}/blueprint`, { cache: "no-store" });
  const data = await readJson(res);
  throwIfBad(res, data, "blueprint failed");
  return data;
}

export async function listRows(
  session: Session | null | undefined,
  slug: string,
  resource: string,
  filters?: Record<string, string>,
  page = 1,
  pageSize = 500
) {
  const q = new URLSearchParams();
  q.set("page", String(page));
  q.set("page_size", String(pageSize));
  if (filters) {
    for (const [k, v] of Object.entries(filters)) {
      if (v) q.set(`filter.${k}`, v);
    }
  }
  const qs = q.toString();
  const path = session?.accessToken
    ? `${PLATFORM}/api/v1/apps/${slug}/${resource}?${qs}`
    : `${PLATFORM}/api/v1/public/apps/${slug}/${resource}?${qs}`;
  // 草稿预览：优先用预览包内嵌行数据（未发布也可出图）
  const prev = getActivePreview();
  const previewSlug = String(prev?.blueprint?.meta?.slug || "");
  if (
    prev &&
    previewSlug === slug &&
    (!resource || resource === prev.resource || !prev.resource)
  ) {
    let items = prev.rows || [];
    if (filters) {
      items = items.filter((row) =>
        Object.entries(filters).every(([k, v]) => !v || String(row[k] ?? "") === String(v))
      );
    }
    const start = Math.max(0, (page - 1) * pageSize);
    return {
      items: items.slice(start, start + pageSize),
      total: items.length,
    };
  }
  const headers: Record<string, string> = {};
  if (session?.accessToken) headers.Authorization = `Bearer ${session.accessToken}`;
  const res = await apiFetch(path, { headers, cache: "no-store" });
  const data = await readJson(res);
  throwIfBad(res, data, "list failed");
  return data as { items: Record<string, unknown>[]; total: number };
}

export async function createRow(
  session: Session | null | undefined,
  slug: string,
  resource: string,
  body: Record<string, unknown>
) {
  if (!session?.accessToken) throw new Error("预览模式为只读，写操作请先登录控制台");
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/${resource}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${session.accessToken}`,
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create failed");
  return data as Record<string, unknown>;
}

export async function updateRow(
  session: Session | null | undefined,
  slug: string,
  resource: string,
  id: string,
  body: Record<string, unknown>
) {
  if (!session?.accessToken) throw new Error("预览模式为只读，写操作请先登录控制台");
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/${resource}/${id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${session.accessToken}`,
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update failed");
  return data as Record<string, unknown>;
}

export async function deleteRow(session: Session | null | undefined, slug: string, resource: string, id: string) {
  if (!session?.accessToken) throw new Error("预览模式为只读，写操作请先登录控制台");
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/${resource}/${id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  if (!res.ok && res.status !== 204) {
    const data = await readJson(res).catch(() => ({}));
    throwIfBad(res, data, "delete failed");
  }
}

export async function aggregate(
  session: Session | null | undefined,
  slug: string,
  resource: string,
  groupBy?: string,
  sum?: string
) {
  const q = new URLSearchParams();
  if (groupBy) q.set("group_by", groupBy);
  if (sum) q.set("sum", sum);
  const headers: Record<string, string> = {};
  if (session?.accessToken) headers.Authorization = `Bearer ${session.accessToken}`;
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/${resource}/aggregate?${q}`, {
    headers,
  });
  const data = await readJson(res);
  throwIfBad(res, data, "aggregate failed");
  return data as {
    total: number;
    group_by?: string;
    sum_field?: string;
    sum?: number;
    buckets?: { key: string; count: number; sum?: number }[];
  };
}

export async function getCapsule(session: Session, slug: string) {
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/agent-capsule`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "capsule failed");
  return data as { capsule: string; format: string; hint: string };
}

export async function importRows(session: Session | null | undefined, slug: string, resource: string, file: File) {
  if (!session?.accessToken) throw new Error("预览模式为只读，写操作请先登录控制台");
  const fd = new FormData();
  fd.append("file", file);
  const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/${resource}/import`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
    body: fd,
  });
  const data = await readJson(res);
  throwIfBad(res, data, "import failed");
  return data;
}

/** @deprecated use importRows */
export const importCSV = importRows;

export async function exportRows(
  session: Session | null | undefined,
  slug: string,
  resource: string,
  format: "xlsx" | "csv" = "xlsx"
) {
  if (!session?.accessToken) throw new Error("预览模式为只读，写操作请先登录控制台");
  const res = await apiFetch(
    `${PLATFORM}/api/v1/apps/${slug}/${resource}/export?format=${format}`,
    { headers: { Authorization: `Bearer ${session.accessToken}` } }
  );
  if (!res.ok) {
    const data = await readJson(res).catch(() => ({}));
    throwIfBad(res, data, "export failed");
  }
  const blob = await res.blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `${resource}.${format === "csv" ? "csv" : "xlsx"}`;
  a.click();
  URL.revokeObjectURL(url);
}

export async function listAudit(session: Session, page = 1) {
  const res = await apiFetch(`/api/v1/audit/logs?page=${page}&page_size=50`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "audit failed");
  return data as { items: any[]; total: number };
}

export type AgentAccount = {
  agent_id: number;
  tenant_id: number;
  name: string;
  client_id: string;
  host_key?: string;
  role_id?: number;
  role_code?: string;
  role_name?: string;
  status: string;
  permissions: string[];
  app_slugs: string[];
  /** Z8b：绑定同步通道 */
  channel_id?: string;
  /** Z8b：绑定线上库 id */
  online_db_id?: string;
  /** Z8b：模块落库名（database_per_app） */
  database_name?: string;
  created_by: number;
  created_at: string;
  last_token_at?: string;
};

export type Role = {
  role_id: number;
  tenant_id: number;
  code: string;
  name: string;
  description: string;
  permissions: string[];
  created_at: string;
};

export async function listAgents(session: Session) {
  const res = await apiFetch(`/api/v1/admin/agents`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list agents failed");
  return data as { items: AgentAccount[] };
}

export async function createAgent(
  session: Session,
  body: {
    name: string;
    role_id: number;
    app_slugs: string[];
    permissions?: string[];
    channel_id?: string;
    online_db_id?: string;
    database_name?: string;
  }
) {
  const res = await apiFetch(`/api/v1/admin/agents`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create agent failed");
  return data as { account: AgentAccount; client_secret: string };
}

export async function updateAgent(
  session: Session,
  id: number,
  body: {
    name?: string;
    status?: string;
    role_id?: number;
    permissions?: string[];
    app_slugs?: string[];
    channel_id?: string;
    online_db_id?: string;
    database_name?: string;
  }
) {
  const res = await apiFetch(`/api/v1/admin/agents/${id}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update agent failed");
  return data as AgentAccount;
}

export async function rotateAgentSecret(session: Session, id: number) {
  const res = await apiFetch(`/api/v1/admin/agents/${id}/rotate-secret`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "rotate secret failed");
  return data as { client_id: string; client_secret: string };
}

export async function deleteAgent(session: Session, id: number) {
  const res = await apiFetch(`/api/v1/admin/agents/${id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "delete agent failed");
}

export async function listRoles(session: Session) {
  const res = await apiFetch(`/api/v1/admin/roles`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list roles failed");
  return data as { items: Role[] };
}

export async function createRole(
  session: Session,
  body: { code: string; name: string; description?: string; permissions: string[] }
) {
  const res = await apiFetch(`/api/v1/admin/roles`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create role failed");
  return data as Role;
}

export async function updateRole(
  session: Session,
  id: number,
  body: { name?: string; description?: string; permissions?: string[] }
) {
  const res = await apiFetch(`/api/v1/admin/roles/${id}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update role failed");
  return data as Role;
}

export async function deleteRole(session: Session, id: number) {
  const res = await apiFetch(`/api/v1/admin/roles/${id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "delete role failed");
}

export type TenantInvite = {
  invite_id: number;
  tenant_id: number;
  code: string;
  role: string;
  org_unit_id?: number;
  max_uses: number;
  used_count: number;
  status: string;
  expires_at?: string;
  created_at: string;
};

export type OrgUnit = {
  org_unit_id: number;
  tenant_id: number;
  parent_id?: number;
  name: string;
  code?: string;
  depth: number;
  path: string;
  created_at: string;
};

export async function acceptInvite(session: Session, code: string) {
  const res = await apiFetch(`/api/v1/auth/invites/accept`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ code }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "accept invite failed");
  return toSession(data);
}

export async function createTenant(session: Session, name: string) {
  const res = await apiFetch(`/api/v1/tenants`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create tenant failed");
  return toSession(data);
}

export async function listInvites(session: Session) {
  const res = await apiFetch(`/api/v1/admin/invites`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list invites failed");
  return data as { items: TenantInvite[] };
}

export async function createInvite(
  session: Session,
  body: { role?: string; org_unit_id?: number; max_uses?: number; expires_in_hours?: number }
) {
  const res = await apiFetch(`/api/v1/admin/invites`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create invite failed");
  return data as TenantInvite;
}

export async function revokeInvite(session: Session, id: number) {
  const res = await apiFetch(`/api/v1/admin/invites/${id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "revoke invite failed");
}

export async function listOrgUnits(session: Session) {
  const res = await apiFetch(`/api/v1/admin/org-units`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list org units failed");
  return data as { items: OrgUnit[]; max_depth: number };
}

export async function createOrgUnit(
  session: Session,
  body: { parent_id?: number; name: string; code?: string }
) {
  const res = await apiFetch(`/api/v1/admin/org-units`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create org unit failed");
  return data as OrgUnit;
}

export async function updateOrgUnit(
  session: Session,
  id: number,
  body: { name?: string; code?: string }
) {
  const res = await apiFetch(`/api/v1/admin/org-units/${id}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update org unit failed");
  return data as OrgUnit;
}

export async function deleteOrgUnit(session: Session, id: number) {
  const res = await apiFetch(`/api/v1/admin/org-units/${id}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "delete org unit failed");
}

export type SyncEndpoint = {
  driver: string;
  dsn: string;
  tables: string[];
};

export type SyncChannel = {
  id: string;
  tenant_id?: number;
  name: string;
  enabled: boolean;
  direction: string;
  conflict_policy: string;
  poll_interval_ms: number;
  local: SyncEndpoint;
  remote: SyncEndpoint;
  pk_columns: Record<string, string>;
  /** 绑定智能体：模块/库归属对照 */
  agent_id?: number;
  /** 公司默认同步通道（Z12） */
  is_system_default?: boolean;
  /** 绑定已发布模块 slug */
  app_slug?: string;
  last_error?: string;
  last_sync_at?: string;
  stats?: {
    pushed_ok: number;
    pushed_applied?: number;
    pushed_skipped?: number;
    pulled_ok: number;
    conflicts: number;
    retries: number;
    last_batch: number;
  };
};

export type SyncTableInspect = {
  name: string;
  row_count: number;
  columns: string[];
  column_count: number;
};

export type SyncInspectResult = {
  ok: boolean;
  side: string;
  driver: string;
  dsn_hint?: string;
  tables: SyncTableInspect[];
  message?: string;
};

export type SyncPreviewResult = {
  ok: boolean;
  table: string;
  columns: string[];
  rows: Record<string, unknown>[];
  total: number;
  limit: number;
  message?: string;
};

export type SyncConflict = {
  id: string;
  channel_id: string;
  table: string;
  row_pk: string;
  op: string;
  source: string;
  payload: string;
  message: string;
  resolved: boolean;
};

export async function listSyncChannels(session: Session) {
  const res = await apiFetch(`/api/v1/admin/sync/channels`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list sync channels failed");
  return data as { items: SyncChannel[] };
}

export async function createSyncChannel(session: Session, body: Partial<SyncChannel>) {
  const res = await apiFetch(`/api/v1/admin/sync/channels`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create sync channel failed");
  return data as SyncChannel;
}

export async function updateSyncChannel(session: Session, id: string, body: Partial<SyncChannel>) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update sync channel failed");
  return data as SyncChannel;
}

export async function deleteSyncChannel(session: Session, id: string) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "delete sync channel failed");
  return data as {
    ok?: boolean;
    deleted_id?: string;
    was_system_default?: boolean;
    recreated_default?: boolean;
    channel_id?: string;
    bindings_fixed?: number;
    agents_fixed?: number;
    heal_error?: string;
  };
}

export async function testSyncEndpoints(
  session: Session,
  body: { local?: SyncEndpoint; remote?: SyncEndpoint; side?: string }
) {
  const res = await apiFetch(`/api/v1/admin/sync/test`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "test sync failed");
  return data as Record<string, unknown>;
}

export async function startSyncChannel(session: Session, id: string) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/start`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "start sync failed");
  return data;
}

export async function stopSyncChannel(session: Session, id: string) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/stop`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "stop sync failed");
  return data;
}

/** @deprecated 公司侧已 403；请用 listPlatformLwwOverrides。预期 403 静默返回空，勿当故障。 */
export async function listSyncConflicts(session: Session, unresolved = true) {
  const q = unresolved ? "?unresolved=1" : "?unresolved=0";
  const res = await apiFetch(`/api/v1/admin/sync/conflicts${q}`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  if (res.status === 403) {
    return { items: [] as SyncConflict[] };
  }
  throwIfBad(res, data, "list conflicts failed (公司侧已禁用，请用超管 LWW 审计)");
  return data as { items: SyncConflict[] };
}

/** @deprecated 公司侧已 403 */
export async function resolveSyncConflict(session: Session, id: string, resolution: string) {
  const res = await apiFetch(`/api/v1/admin/sync/conflicts/${encodeURIComponent(id)}/resolve`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ resolution }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "resolve conflict failed (公司侧已禁用)");
  return data;
}

export async function reconcileSyncChannel(session: Session, id: string) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/reconcile`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "reconcile failed");
  return data as {
    channel_id: string;
    reports: Array<{
      table: string;
      only_local: string[];
      only_remote: string[];
      patched_push: number;
      patched_pull: number;
    }>;
    message: string;
  };
}

export type SyncCheckpointSlot = {
  synced_at?: string;
  source?: string;
  table_count?: number;
  row_count?: number;
};

export type SyncCheckpointMeta = {
  channel_id?: string;
  latest?: SyncCheckpointSlot | null;
  previous?: SyncCheckpointSlot | null;
};

export async function getSyncCheckpointMeta(session: Session, id: string) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/checkpoint`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "get checkpoint failed");
  return data as SyncCheckpointMeta;
}

export async function restoreSyncChannel(
  session: Session,
  id: string,
  body: { which?: "latest" | "previous"; confirm: boolean }
) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/restore`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "restore failed");
  return data as {
    ok?: boolean;
    which?: string;
    synced_at?: string;
    source?: string;
    tables?: number;
    upserted?: number;
    deleted?: number;
    restored_at?: string;
  };
}

/** 外部源 C → 写入本地 B，再经 outbox 同步到线上 A */
export async function ingestSyncRows(
  session: Session,
  id: string,
  body: { table: string; source?: string; rows: Record<string, unknown>[] }
) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/ingest`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "ingest failed");
  return data as { ok: boolean; ingested: number; hint?: string };
}

/** 控制台验同步：列出通道线上/本机库的表、行数、字段 */
export async function inspectSyncChannel(
  session: Session,
  id: string,
  opts?: { side?: "remote" | "local"; include_sync_meta?: boolean }
) {
  const side = opts?.side || "remote";
  const q = new URLSearchParams({ side });
  if (opts?.include_sync_meta) q.set("include_sync_meta", "1");
  const res = await apiFetch(
    `/api/v1/admin/sync/channels/${encodeURIComponent(id)}/inspect?${q}`,
    { headers: { Authorization: `Bearer ${session.accessToken}` } }
  );
  const data = await readJson(res);
  throwIfBad(res, data, "inspect failed");
  return data as SyncInspectResult;
}

/** 预览单表内容 */
export async function previewSyncTable(
  session: Session,
  id: string,
  body: { table: string; side?: "remote" | "local"; limit?: number }
) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/preview`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "preview failed");
  return data as SyncPreviewResult;
}

/** 控制台删业务表（仅当前 side；不同步 DDL 到另一侧） */
export async function dropSyncTable(
  session: Session,
  id: string,
  body: { table: string; side?: "remote" | "local" }
) {
  const res = await apiFetch(`/api/v1/admin/sync/channels/${encodeURIComponent(id)}/drop-table`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "drop table failed");
  return data as {
    ok: boolean;
    side: string;
    table: string;
    dropped: boolean;
    meta_cleared?: number;
    message?: string;
  };
}

export async function ensureSyncBinding(
  session: Session,
  body: {
    local_database_id: string;
    online_db_id: string;
    channel_id?: string;
    database_name?: string;
    display_name?: string;
    note?: string;
    user_id?: number;
    shared?: boolean;
  }
) {
  const res = await apiFetch(`/api/v1/admin/sync/bindings`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "ensure binding failed");
  return data as SyncBinding;
}

export type SyncBinding = {
  id: string;
  tenant_id: number;
  user_id?: number;
  local_database_id: string;
  online_db_id: string;
  channel_id?: string;
  database_name?: string;
  display_name?: string;
  shared?: boolean;
  note?: string;
  created_at?: string;
  updated_at?: string;
};

export type BindCode = {
  code: string;
  tenant_id: number;
  channel_id?: string;
  online_db_id?: string;
  database_name?: string;
  max_uses: number;
  used_count: number;
  revoked?: boolean;
  expires_at?: string;
  created_by?: number;
  created_at?: string;
  note?: string;
};

export async function listBindCodes(session: Session) {
  const res = await apiFetch(`/api/v1/admin/bind-codes`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list bind codes failed");
  return data as { items: BindCode[] };
}

export async function createBindCode(
  session: Session,
  body?: {
    channel_id?: string;
    online_db_id?: string;
    database_name?: string;
    max_uses?: number;
    expires_hours?: number;
    note?: string;
  }
) {
  const res = await apiFetch(`/api/v1/admin/bind-codes`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body || {}),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create bind code failed");
  return data as BindCode;
}

export async function revokeBindCode(session: Session, code: string) {
  const res = await apiFetch(`/api/v1/admin/bind-codes/${encodeURIComponent(code)}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "revoke bind code failed");
  return data as { ok: boolean };
}

export async function listSyncBindings(session: Session, localDatabaseId?: string) {
  const q = localDatabaseId
    ? `?local_database_id=${encodeURIComponent(localDatabaseId)}`
    : "";
  const res = await apiFetch(`/api/v1/admin/sync/bindings${q}`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list bindings failed");
  return data as { items: SyncBinding[] };
}

export async function listPlatformLwwOverrides(
  session: Session,
  opts?: { tenant_id?: number; channel_id?: string; limit?: number }
) {
  const q = new URLSearchParams();
  if (opts?.tenant_id) q.set("tenant_id", String(opts.tenant_id));
  if (opts?.channel_id) q.set("channel_id", opts.channel_id);
  if (opts?.limit) q.set("limit", String(opts.limit));
  const qs = q.toString();
  const res = await apiFetch(`/api/v1/platform/dbsync/lww-overrides${qs ? `?${qs}` : ""}`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list lww overrides failed");
  return data as {
    items: Array<{
      id: string;
      tenant_id: number;
      channel_id: string;
      table: string;
      row_pk: string;
      op: string;
      entry: string;
      policy: string;
      outcome: string;
      loser_payload?: string;
      winner_payload?: string;
      target_ver: number;
      source_ver: number;
      created_at: string;
    }>;
    hint?: string;
  };
}

export async function rollbackPlatformLwwOverride(session: Session, id: string) {
  const res = await apiFetch(
    `/api/v1/platform/dbsync/lww-overrides/${encodeURIComponent(id)}/rollback`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${session.accessToken}` },
    }
  );
  const data = await readJson(res);
  throwIfBad(res, data, "lww rollback failed");
  return data as { ok: boolean; record?: Record<string, unknown>; hint?: string };
}

export async function listPlatformTenants(session: Session) {
  const res = await apiFetch(`/api/v1/platform/tenants`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list tenants failed");
  return data as {
    items: Array<{
      tenant_id: number;
      name: string;
      slug: string;
      created_at: string;
      user_count: number;
      app_count: number;
    }>;
  };
}

export async function listPlatformPermModules(session: Session) {
  const res = await apiFetch(`/api/v1/platform/perm-modules`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list perm modules failed");
  return data as {
    modules: { title: string; items: { perm: string; desc: string }[] }[];
    catalog: string[];
    platform: string[];
  };
}

export async function getPlatformTenantPerms(session: Session, tenantId: number) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/permissions`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "get tenant perms failed");
  return data as { tenant_id: number; permissions: string[]; total: number };
}

export async function setPlatformTenantPerms(session: Session, tenantId: number, permissions: string[]) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/permissions`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ permissions }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "set tenant perms failed");
  return data as { tenant_id: number; permissions: string[]; total: number };
}

export async function createPlatformTenant(
  session: Session,
  name: string,
  withAdminInvite = false,
  slug = "",
  adminPhone = ""
) {
  const res = await apiFetch(`/api/v1/platform/tenants`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name,
      slug: slug || undefined,
      with_admin_invite: withAdminInvite,
      admin_phone: adminPhone.trim() || undefined,
    }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create tenant failed");
  return data as {
    tenant: { tenant_id: number; name: string; slug: string };
    admin_account?: {
      user_id: number;
      username: string;
      password: string;
      phone?: string;
      display_name: string;
      role: string;
    };
    admin_invite?: { code: string; role: string; expires_at?: string };
  };
}

export async function updatePlatformTenant(
  session: Session,
  tenantId: number,
  name: string,
  slug = ""
) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name, slug: slug || undefined }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update tenant failed");
  return data as { tenant_id: number; name: string; slug: string };
}

export async function issuePlatformAdminInvite(session: Session, tenantId: number) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/admin-invite`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "admin invite failed");
  return data as { code: string; role: string; expires_at?: string };
}

export async function issuePlatformAdminAccount(session: Session, tenantId: number, phone: string) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/admin-account`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ phone: phone.trim() }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "admin account failed");
  return data as {
    admin_account: {
      user_id: number;
      username: string;
      password: string;
      phone?: string;
      display_name: string;
      role: string;
    };
  };
}

export type PlatformAdminInfo = {
  user_id: number;
  username: string;
  phone: string;
  username_login_disabled?: boolean;
  display_name: string;
  role: string;
  status: string;
};

export async function listPlatformTenantAdmins(session: Session, tenantId: number) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/admins`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list admins failed");
  return data as { items: PlatformAdminInfo[] };
}

export async function updatePlatformTenantAdmin(
  session: Session,
  tenantId: number,
  userId: number,
  body: { reset_password?: boolean; password?: string; phone?: string; disable_username_login?: boolean }
) {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/admins/${userId}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update admin failed");
  return data as {
    admin_account: {
      user_id: number;
      username: string;
      password?: string;
      phone?: string;
      display_name: string;
      role: string;
    };
  };
}

export async function enterPlatformTenant(session: Session, tenantId: number): Promise<Session> {
  const res = await apiFetch(`/api/v1/platform/tenants/${tenantId}/enter`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "enter tenant failed");
  return toSession(data);
}

export async function exitPlatformTenant(session: Session): Promise<Session> {
  const res = await apiFetch(`/api/v1/platform/exit`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "exit tenant failed");
  return toSession(data);
}

export async function getCompanyEntitlements(session: Session) {
  const res = await apiFetch(`/api/v1/admin/entitlements`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "get entitlements failed");
  return data as {
    permissions: string[];
    modules: { title: string; items: { perm: string; desc: string }[] }[];
  };
}

export type Member = {
  user_id: number;
  username: string;
  phone?: string;
  display_name: string;
  role: string;
  org_unit_id: number;
  status: string;
  created_at?: string;
};

export async function listMembers(session: Session) {
  const res = await apiFetch(`/api/v1/admin/members`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "list members failed");
  return data as { items: Member[] };
}

export async function createMember(
  session: Session,
  body: {
    password?: string;
    display_name?: string;
    role?: string;
    org_unit_id?: number;
  }
) {
  const res = await apiFetch(`/api/v1/admin/members`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "create member failed");
  return data as Member & { password: string };
}

export async function changePassword(session: Session, oldPassword: string, newPassword: string) {
  const res = await apiFetch(`/api/v1/auth/password`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ old_password: oldPassword, new_password: newPassword }),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "change password failed");
  return data as { ok: boolean };
}

export async function fetchMe(session: Session) {
  const res = await apiFetch(`/api/v1/auth/me`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const data = await readJson(res);
  throwIfBad(res, data, "fetch me failed");
  return data as {
    user_id: number;
    username: string;
    phone: string;
    display_name: string;
    role: string;
    tenant_id: number;
    status: string;
  };
}

export async function bindPhone(
  session: Session,
  phone: string,
  disableUsernameLogin?: boolean
) {
  const body: { phone: string; disable_username_login?: boolean } = { phone };
  if (disableUsernameLogin !== undefined) {
    body.disable_username_login = disableUsernameLogin;
  }
  const res = await apiFetch(`/api/v1/auth/phone`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "bind phone failed");
  return data as {
    user_id: number;
    username: string;
    phone: string;
    username_login_disabled: boolean;
    display_name: string;
  };
}

export async function updateMember(
  session: Session,
  id: number,
  body: { role?: string; org_unit_id?: number; status?: string; phone?: string }
) {
  const res = await apiFetch(`/api/v1/admin/members/${id}`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data = await readJson(res);
  throwIfBad(res, data, "update member failed");
  return data as Member;
}

export async function uploadFile(session: Session, file: File) {
  const fd = new FormData();
  fd.append("file", file);
  const res = await apiFetch(`/api/v1/storage`, {
    method: "POST",
    headers: { Authorization: `Bearer ${session.accessToken}` },
    body: fd,
  });
  const data = await readJson(res);
  throwIfBad(res, data, "upload failed");
  return data as { key: string; url: string; filename: string };
}
