export type SaasUser = {
  id: number;
  email: string;
  displayName: string;
  avatarUrl: string | null;
  role: 'user' | 'admin' | string;
  status: string;
  forcePasswordChange?: boolean;
  createdAt?: string;
  lastLoginAt?: string | null;
  provider?: string;
};

export type SaasEntitlement = {
  tier: 'free' | 'pro';
  pro: boolean;
  blocked: boolean;
  expired?: boolean;
  projectLimit?: number | null;
  accountProjectMetadataLimit?: number | null;
  quota: number;
  usage: number;
  remaining: number;
  license?: {
    id: number;
    key: string;
    provider?: string | null;
    productLink?: string | null;
    type?: string;
    verifiedAt?: string | null;
    lastVerifiedAt?: string | null;
    expiresAt?: string | null;
    expired?: boolean;
  } | null;
};

export type SaasAuthState =
  | { authenticated: false }
  | { authenticated: true; csrfToken: string; user: SaasUser; entitlement: SaasEntitlement };

export type SaasSafeSettings = {
  theme?: 'light' | 'dark' | 'system';
};

export type CloudProject = {
  id: string;
  name: string;
  projectSlug: string;
  storageType: 'browser' | 'linked-folder' | 'imported';
  templateId?: string | null;
  templateVersion?: string | null;
  templateTier?: 'free' | 'pro' | null;
  status: 'active' | 'archived';
  visibility?: 'private' | 'shared' | 'authenticated_public';
  createdAt?: string;
  updatedAt?: string;
  lastOpenedAt?: string | null;
};



export type PublicSaasConfig = {
  saasEnabled: boolean;
  googleAuthEnabled: boolean;
  githubAuthEnabled: boolean;
  magicEmailEnabled: boolean;
  captcha?: { provider: string; siteKey: string };
  contactEnabled: boolean;
  shopUrl: string;
  monthlyShopUrl?: string;
  lifetimePrice?: string;
  googleTagId?: string;
  freeAccountProjectMetadataLimit?: number;
  /** @deprecated Account metadata quota alias. */
  freePlanProjectLimit?: number;
  cloudCompile?: { enabled: boolean; allowedEngines: string[]; maxFiles: number; maxProjectBytes: number; maxCompileSeconds: number; maxOutputBytes: number; concurrency: number; dailyAllowance: number; monthlyAllowance: number; freeFallbackEnabled: boolean };
  linuxTerminal?: { enabled: boolean; executionImage: string; maxProjectBytes: number; maxDownloadBytes: number; sessionMinutes: number; tools: string[] };
  homeAiBuilder?: { enabled: boolean; anonymousVisible: boolean; freeUserEnabled: boolean; maxPromptLength: number; quickStarts: string[] };
  preview?: { isolatedOrigin: boolean; publicUrl: string };
};

export class SaasApiError extends Error {
  constructor(public readonly status: number, public readonly code: string, message?: string, public readonly details?: Record<string, unknown>) {
    super(message || code);
    this.name = 'SaasApiError';
  }
}

async function parseResponse<T>(response: Response): Promise<T> {
  const payload = await response.json().catch(() => ({})) as Record<string, unknown>;
  if (!response.ok) {
    const code = String(payload.error || `http_${response.status}`);
    const details = payload.details && typeof payload.details === 'object' ? payload.details as Record<string, unknown> : undefined;
    const configuredLimit = Number(details?.limit);
    const message = (code === 'account_project_metadata_limit_reached' || code === 'project_limit_reached') && Number.isInteger(configuredLimit) && configuredLimit > 0
      ? `Free account metadata can remember up to ${configuredLimit} projects. Your local project remains available on this device.`
      : typeof payload.message === 'string' && payload.message
        ? payload.message
        : code;
    throw new SaasApiError(response.status, code, message, details);
  }
  return payload as T;
}

export async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
  const response = await fetch(path, {
    credentials: 'same-origin',
    cache: 'no-store',
    ...init,
    headers: {
      Accept: 'application/json',
      ...(init.body ? { 'Content-Type': 'application/json' } : {}),
      ...(init.headers || {}),
    },
  });
  return parseResponse<T>(response);
}

export async function getPublicSaasConfig(): Promise<PublicSaasConfig> {
  return request<PublicSaasConfig>('/api/config/public');
}

export async function getAuthState(): Promise<SaasAuthState> {
  return request<SaasAuthState>('/api/auth/me');
}

async function terminalMutation<T>(path: string, method: string, body?: unknown): Promise<T> {
  const auth = await getAuthState().catch(() => ({ authenticated: false } as SaasAuthState));
  return request<T>(path, {
    method,
    headers: auth.authenticated ? { 'x-learnitive-csrf': auth.csrfToken } : {},
    body: body === undefined ? undefined : JSON.stringify(body),
  });
}

export async function authenticatedMutation<T>(path: string, method: string, body?: unknown): Promise<T> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  return request<T>(path, {
    method,
    headers: { 'x-learnitive-csrf': auth.csrfToken },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
}

export async function requestMagicLogin(email: string, captchaToken: string, returnTo = '/projects'): Promise<{ ok: true }> {
  return request('/api/auth/magic/request', {
    method: 'POST',
    body: JSON.stringify({ email, captchaToken, returnTo }),
  });
}

export async function consumeMagicLogin(token: string): Promise<{ ok: true; returnTo: string; created: boolean }> {
  return request('/api/auth/magic/consume', {
    method: 'POST',
    body: JSON.stringify({ token }),
  });
}

export async function logout(): Promise<void> {
  await authenticatedMutation('/api/auth/logout', 'POST', {});
}

export async function updateProfile(displayName: string): Promise<void> {
  await authenticatedMutation('/api/user/profile', 'PATCH', { displayName });
}

export async function getSafeSettings(): Promise<SaasSafeSettings> {
  return (await request<{ settings: SaasSafeSettings }>('/api/user/settings')).settings || {};
}

export async function updateSafeSettings(settings: SaasSafeSettings): Promise<SaasSafeSettings> {
  return (await authenticatedMutation<{ settings: SaasSafeSettings }>('/api/user/settings', 'PATCH', settings)).settings || {};
}

export async function updateSafeSettingsIfAuthenticated(settings: SaasSafeSettings): Promise<void> {
  try {
    const auth = await getAuthState();
    if (!auth.authenticated) return;
    await updateSafeSettings(settings);
  } catch (error) {
    console.warn('[Learnitive] Safe account preference sync is unavailable; the local preference remains authoritative.', error);
  }
}


export async function deleteAccount(): Promise<{ localFilesDeleted: false }> {
  return authenticatedMutation('/api/user/account', 'DELETE', { confirm: 'DELETE' });
}

export async function verifyAccountLicense(licenseKey: string): Promise<{ entitlement: string; publicKey: string; account: SaasEntitlement }> {
  return authenticatedMutation('/api/licenses/verify', 'POST', { licenseKey });
}

export async function getCloudProject(id: string): Promise<{ project: CloudProject; role: 'owner' | 'editor' | 'viewer'; permissions: Record<string, boolean> }> {
  // Legacy explicit share/publication compatibility only. Normal local projects are never registered here.
  return request(`/api/projects/${encodeURIComponent(id)}`);
}

export type LinuxTerminalPolicy = { sandboxTier?: string; storageLimitBytes?: number; uploadMaxBytes?: number; downloadMaxBytes?: number; idleTimeoutSeconds?: number; workspaceRetentionSeconds?: number; memoryMb?: number; cpuLimit?: number; pidLimit?: number; networkMode?: 'off' | 'packages-git' | 'internet' | string };
export type LinuxTerminalSession = { sessionId: string; token: string; expiresAt: string; wsPath: string; projectPath?: string; sandboxExpiresAt?: string; profile?: string; generation?: number; policy?: LinuxTerminalPolicy; planId?: string; guest?: boolean };
export type TerminalWorkspaceStatus = { userId?: string; containerName?: string; generation?: number; createdAt?: string; expiresAt?: string; status?: string; sandboxTier?: string; storageLimitBytes?: number; storageUsedBytes?: number; idleTimeoutSeconds?: number; workspaceRetentionSeconds?: number; uploadMaxBytes?: number; downloadMaxBytes?: number; memoryMb?: number; cpuLimit?: number; pidLimit?: number; networkMode?: string; recovered?: boolean; restarted?: boolean; preservedWorkspace?: boolean; workspaceRemoved?: boolean; containerRemoved?: boolean; destroyedAt?: string; closedSessions?: number; planId?: string };
export type LinuxTerminalEntry = { path: string; name: string; kind: 'file' | 'directory'; size: number; modifiedAt: string };

export type CollaborationHostGrant = { grant: string; expiresAt: string };

export async function createCollaborationHostGrant(roomKey: string, reviewVersion: number): Promise<CollaborationHostGrant> {
  return authenticatedMutation<CollaborationHostGrant>('/api/collaboration/host-grant', 'POST', { roomKey, reviewVersion });
}

export async function createLinuxTerminalSession(projectId: string, projectName?: string): Promise<LinuxTerminalSession> {
  return authenticatedMutation<LinuxTerminalSession>('/api/terminal/sessions', 'POST', {
    projectId,
    ...(projectName ? {projectName} : {}),
  });
}

export async function closeLinuxTerminalSessions(): Promise<{ ok: boolean; closedSessions: number }> {
  return terminalMutation('/api/terminal/sessions/close-all', 'POST', {});
}

export async function createHomepageTerminalSession(): Promise<LinuxTerminalSession> {
  return terminalMutation<LinuxTerminalSession>('/api/terminal/homepage/session', 'POST', {});
}

export async function heartbeatGuestTerminalSession(sessionId: string): Promise<{ ok: boolean; leaseSeconds?: number }> {
  return request('/api/terminal/guest/heartbeat', { method: 'POST', body: JSON.stringify({ sessionId }) });
}

export function releaseGuestTerminalSessionKeepalive(sessionId: string): void {
  if (typeof window === 'undefined' || typeof navigator === 'undefined') return;
  const body = JSON.stringify({ sessionId });
  try {
    if (typeof navigator.sendBeacon === 'function') {
      const payload = new Blob([body], { type: 'application/json' });
      if (navigator.sendBeacon('/api/terminal/guest/release', payload)) return;
    }
  } catch {
    // Fall through to keepalive fetch.
  }
  void fetch('/api/terminal/guest/release', {
    method: 'POST', credentials: 'same-origin', cache: 'no-store', keepalive: true,
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body,
  }).catch(() => undefined);
}

export async function refreshLinuxTerminalToken(sessionId: string): Promise<Pick<LinuxTerminalSession, 'token' | 'expiresAt' | 'wsPath'>> {
  return terminalMutation(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/token`, 'POST', {});
}

export async function uploadLinuxTerminalFiles(sessionId: string, files: Array<{path: string; content: string}>): Promise<{uploadedFiles: number; uploadedBytes: number}> {
  return terminalMutation(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/files`, 'POST', {files});
}

export type TerminalWorkspaceManifestFile = { path: string; size: number; modifiedAt: string; sha256: string };
export type TerminalWorkspaceManifest = { root: string; files: TerminalWorkspaceManifestFile[] };

export async function uploadLinuxTerminalFileChunk(sessionId: string, path: string, offset: number, final: boolean, bytes: Uint8Array): Promise<{ok: boolean; path: string; offset: number; complete: boolean; uploadedBytes: number}> {
  const auth = await getAuthState().catch(() => ({ authenticated: false } as SaasAuthState));
  const query = new URLSearchParams({ path, offset: String(Math.max(0, offset || 0)), final: final ? '1' : '0' });
  const response = await fetch(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/chunk?${query}`, {
    method: 'PUT', credentials: 'same-origin', cache: 'no-store', body: bytes,
    headers: { 'Content-Type': 'application/octet-stream', ...(auth.authenticated ? { 'x-learnitive-csrf': auth.csrfToken } : {}) },
  });
  return parseResponse(response);
}

export async function getLinuxTerminalWorkspaceManifest(sessionId: string, path = 'projects'): Promise<TerminalWorkspaceManifest> {
  const query = new URLSearchParams({ path });
  return request<TerminalWorkspaceManifest>(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/manifest?${query}`);
}

export async function deleteLinuxTerminalWorkspaceEntry(sessionId: string, path: string): Promise<void> {
  await terminalMutation(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/files`, 'DELETE', { path });
}

export async function downloadLinuxTerminalWorkspace(sessionId: string): Promise<Blob> {
  const response = await fetch(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/archive`, { credentials: 'same-origin', cache: 'no-store' });
  if (!response.ok) {
    const payload = await response.json().catch(() => ({})) as Record<string, unknown>;
    throw new SaasApiError(response.status, String(payload.error || `http_${response.status}`));
  }
  return response.blob();
}

export async function listLinuxTerminalFiles(sessionId: string, path = '.', recursive = false): Promise<LinuxTerminalEntry[]> {
  const query = new URLSearchParams({path, recursive: recursive ? '1' : '0'});
  return (await request<{entries: LinuxTerminalEntry[]}>(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/files?${query}`)).entries;
}

export async function fetchLinuxTerminalFile(sessionId: string, path: string): Promise<{blob: Blob; filename: string}> {
  const query = new URLSearchParams({path});
  const response = await fetch(`/api/terminal/sessions/${encodeURIComponent(sessionId)}/download?${query}`, {credentials: 'same-origin', cache: 'no-store'});
  if (!response.ok) {
    const payload = await response.json().catch(() => ({})) as Record<string, unknown>;
    throw new SaasApiError(response.status, String(payload.error || `http_${response.status}`));
  }
  const disposition = response.headers.get('content-disposition') || '';
  const filename = disposition.match(/filename="([^"]+)"/u)?.[1] || path.split('/').filter(Boolean).at(-1) || 'download';
  return {blob: await response.blob(), filename};
}

export async function destroyLinuxTerminalSession(sessionId: string): Promise<void> {
  await terminalMutation(`/api/terminal/sessions/${encodeURIComponent(sessionId)}`, 'DELETE', {});
}

export async function restartTerminalWorkspace(): Promise<TerminalWorkspaceStatus> { return terminalMutation('/api/terminal/workspace/restart', 'POST', {}); }
export async function destroyTerminalWorkspace(): Promise<TerminalWorkspaceStatus> { return terminalMutation('/api/terminal/workspace/destroy', 'POST', {}); }
export async function createFreshTerminalWorkspace(): Promise<TerminalWorkspaceStatus> { return terminalMutation('/api/terminal/workspace/fresh', 'POST', {}); }
export async function recoverTerminalWorkspace(): Promise<TerminalWorkspaceStatus> { return terminalMutation('/api/terminal/workspace/recover', 'POST', {}); }

export type ProjectEnvironmentMetrics = { cpuPercent:number|null; memoryUsedBytes:number|null; memoryLimitBytes:number|null; pids:number|null; uptimeSeconds:number; cpuLimit:number|null; pidsLimit:number|null; networkIo:string; blockIo:string };
export type ProjectEnvironment = { userId:string; containerName:string; generation:number; createdAt:string; expiresAt:string; runtimeImage:string; status:string; projectId?:string; workingTree?:string; recreated?:boolean; entitlementUpgraded?:boolean; sandboxTier?:'free'|'pro1'|'pro2'|'pro3'; storageLimitBytes?:number; storageUsedBytes?:number; quotaBackend?:'soft'|'hard'|string; metrics?:ProjectEnvironmentMetrics };
export type ProjectProcess = { processKey:string; status:'running'|'stopped'|string; port:number|null };
export async function ensureProjectEnvironment(projectId:string): Promise<ProjectEnvironment> { return (await authenticatedMutation<{environment:ProjectEnvironment}>(`/api/projects/${encodeURIComponent(projectId)}/environment`,'POST',{})).environment; }
export async function restartProjectEnvironment(projectId:string): Promise<ProjectEnvironment> { return (await authenticatedMutation<{environment:ProjectEnvironment}>(`/api/projects/${encodeURIComponent(projectId)}/environment/restart`,'POST',{})).environment; }
export async function resetProjectEnvironment(projectId:string): Promise<ProjectEnvironment> { return (await authenticatedMutation<{environment:ProjectEnvironment}>(`/api/projects/${encodeURIComponent(projectId)}/environment/reset`,'POST',{})).environment; }
export type ProjectEnvironmentFileListing = { entries:LinuxTerminalEntry[]; environment:ProjectEnvironment };
export async function listProjectEnvironmentFiles(projectId:string, recursive=true): Promise<ProjectEnvironmentFileListing> { const q=new URLSearchParams({path:'.',recursive:recursive?'1':'0'}); return request<ProjectEnvironmentFileListing>(`/api/projects/${encodeURIComponent(projectId)}/environment/files?${q}`); }
export async function uploadProjectEnvironmentFiles(projectId:string, files:Array<{path:string;content:string}>): Promise<{uploadedFiles:number;uploadedBytes:number}> { return authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/environment/files`,'POST',{files}); }
export async function deleteProjectEnvironmentFile(projectId:string,path:string): Promise<void> { await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/environment/files`,'DELETE',{path}); }
export async function fetchProjectEnvironmentFile(projectId:string,path:string): Promise<Uint8Array> { const q=new URLSearchParams({path}); const response=await fetch(`/api/projects/${encodeURIComponent(projectId)}/environment/download?${q}`,{credentials:'same-origin',cache:'no-store'}); if(!response.ok) throw new SaasApiError(response.status,'environment_download_failed'); return new Uint8Array(await response.arrayBuffer()); }
export async function runProjectSandboxCommand(projectId:string,command:string,timeoutSeconds?:number): Promise<{exitCode:number;stdout:string;stderr:string;truncated?:boolean}> { return authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/commands`,'POST',{command,timeoutSeconds}); }
export async function startProjectSandboxProcess(projectId:string,input:{command:string;port?:number;processKey?:string}): Promise<ProjectProcess> { return authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/processes`,'POST',input); }
export async function listProjectSandboxProcesses(projectId:string): Promise<ProjectProcess[]> { return (await request<{processes:ProjectProcess[]}>(`/api/projects/${encodeURIComponent(projectId)}/processes`)).processes; }
export async function stopProjectSandboxProcess(projectId:string,processKey:string): Promise<void> { await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/processes/${encodeURIComponent(processKey)}`,'DELETE',{}); }
export async function getProjectSandboxProcessLogs(projectId:string,processKey:string): Promise<string> { return (await request<{logs:string}>(`/api/projects/${encodeURIComponent(projectId)}/processes/${encodeURIComponent(processKey)}/logs`)).logs; }
export function projectSandboxPreviewUrl(projectId:string,processKey:string): string { return `/api/projects/${encodeURIComponent(projectId)}/processes/${encodeURIComponent(processKey)}/preview/`; }
export async function getProjectSandboxPreviewAccess(projectId:string,processKey:string): Promise<{url:string;isolated:boolean;expiresAt?:string}> { return request(`/api/projects/${encodeURIComponent(projectId)}/processes/${encodeURIComponent(processKey)}/preview-access`); }

export type ProjectMember = { userId:number; role:'owner'|'editor'|'viewer'; email:string; displayName:string };
export type ProjectShareLink = { id:number; permission:'view'|'edit'; expiresAt?:string|null; revokedAt?:string|null; createdAt?:string|null; token?:string };
export type ProjectSecret = { name:string; configured:true; createdAt?:string; updatedAt?:string };
export type ProjectCheckpoint = { id:number; kind:string; label?:string|null; createdAt?:string; revertedAt?:string|null };
export type ProjectAiHistory = {
  thread: null | { id:number; title?:string|null; createdBy:number; createdAt?:string; updatedAt?:string };
  messages: Array<{id:number; userId?:number|null; role:'user'|'assistant'; content:string; createdAt:string}>;
  tasks: Array<{id:number; status:'queued'|'planning'|'running'|'waiting_for_tool'|'verifying'|'completed'|'failed'|'cancelled'; requestSummary?:string|null; resultSummary?:string|null; errorSummary?:string|null; initiatedBy:number; createdAt?:string; startedAt?:string|null; completedAt?:string|null; updatedAt?:string}>;
};
export type ProjectRuntimeSettings = { runtimeMode?:string|null; workingDirectory:string; buildCommand?:string|null; startCommand?:string|null; testCommand?:string|null; previewPort?:number|null; updatedAt?:string|null };
export type ProjectPublication = { id?:number; version:number; createdAt?:string; revokedAt?:string|null; manifest?:{fileCount?:number;totalBytes?:number;source?:string}|null; snapshot?:Record<string,string> };
export type AccountUsageDay={date:string;sandboxCreated:number;sandboxRecreated:number;aiTasks:number;commands:number;processStarts:number;storagePeakBytes:number};
export type DiscoveredProject = CloudProject & { publication: ProjectPublication };

export async function listProjectMembers(projectId:string):Promise<ProjectMember[]>{return (await request<{members:ProjectMember[]}>(`/api/projects/${encodeURIComponent(projectId)}/members`)).members;}
export async function addProjectMember(projectId:string,email:string,role:'editor'|'viewer'):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/members`,'POST',{email,role});}
export async function removeProjectMember(projectId:string,userId:number):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/members/${userId}`,'DELETE',{});}
export async function listProjectShareLinks(projectId:string):Promise<ProjectShareLink[]>{return (await request<{links:ProjectShareLink[]}>(`/api/projects/${encodeURIComponent(projectId)}/share-links`)).links;}
export async function createProjectShareLink(projectId:string,permission:'view'|'edit'='view',expiresAt?:string):Promise<ProjectShareLink>{return (await authenticatedMutation<{link:ProjectShareLink}>(`/api/projects/${encodeURIComponent(projectId)}/share-links`,'POST',{permission,expiresAt})).link;}
export async function revokeProjectShareLink(projectId:string,linkId:number):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/share-links/${linkId}`,'DELETE',{});}
export async function redeemProjectShareToken(token:string):Promise<{projectId:string;permission:'view'|'edit'}>{return authenticatedMutation(`/api/share/${encodeURIComponent(token)}/redeem`,'POST',{});}
export async function listProjectSecrets(projectId:string):Promise<ProjectSecret[]>{return (await request<{secrets:ProjectSecret[]}>(`/api/projects/${encodeURIComponent(projectId)}/secrets`)).secrets;}
export async function setProjectSecret(projectId:string,name:string,value:string):Promise<ProjectSecret>{return (await authenticatedMutation<{secret:ProjectSecret}>(`/api/projects/${encodeURIComponent(projectId)}/secrets`,'POST',{name,value})).secret;}
export async function removeProjectSecret(projectId:string,name:string):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/secrets/${encodeURIComponent(name)}`,'DELETE',{});}
export async function createProjectCheckpoint(projectId:string,label:string,snapshot:Record<string,string>):Promise<{id:number}>{return (await authenticatedMutation<{checkpoint:{id:number}}>(`/api/projects/${encodeURIComponent(projectId)}/checkpoints`,'POST',{label,snapshot})).checkpoint;}
export async function listProjectCheckpoints(projectId:string):Promise<ProjectCheckpoint[]>{return (await request<{checkpoints:ProjectCheckpoint[]}>(`/api/projects/${encodeURIComponent(projectId)}/checkpoints`)).checkpoints;}
export async function revertProjectCheckpoint(projectId:string,checkpointId:number):Promise<Record<string,string>>{return (await authenticatedMutation<{snapshot:Record<string,string>}>(`/api/projects/${encodeURIComponent(projectId)}/checkpoints/${checkpointId}/revert`,'POST',{})).snapshot;}
export async function getProjectAiHistory(projectId:string):Promise<ProjectAiHistory>{return request<ProjectAiHistory>(`/api/projects/${encodeURIComponent(projectId)}/ai/history`);}
export async function createProjectAiThread(projectId:string,title?:string):Promise<{id:number;title?:string|null}>{return (await authenticatedMutation<{thread:{id:number;title?:string|null}}>(`/api/projects/${encodeURIComponent(projectId)}/ai/threads`,'POST',{title})).thread;}
export async function appendProjectAiMessage(projectId:string,threadId:number,role:'user'|'assistant',content:string):Promise<{id:number}>{return (await authenticatedMutation<{message:{id:number}}>(`/api/projects/${encodeURIComponent(projectId)}/ai/messages`,'POST',{threadId,role,content})).message;}
export async function createProjectAiTask(projectId:string,threadId:number,requestSummary:string):Promise<{id:number;status:string}>{return (await authenticatedMutation<{task:{id:number;status:string}}>(`/api/projects/${encodeURIComponent(projectId)}/ai/tasks`,'POST',{threadId,requestSummary})).task;}
export async function updateProjectAiTask(projectId:string,taskId:number,input:{status:ProjectAiHistory['tasks'][number]['status'];resultSummary?:string;errorSummary?:string}):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/ai/tasks/${taskId}`,'PATCH',input);}
export async function recordProjectAiToolAction(projectId:string,input:{taskId:number;toolName:string;status?:'started'|'completed'|'failed'|'cancelled';inputSummary?:string;resultSummary?:string}):Promise<{id:number}>{return (await authenticatedMutation<{action:{id:number}}>(`/api/projects/${encodeURIComponent(projectId)}/ai/tool-actions`,'POST',input)).action;}
export async function getProjectRuntimeSettings(projectId:string):Promise<ProjectRuntimeSettings>{return (await request<{settings:ProjectRuntimeSettings}>(`/api/projects/${encodeURIComponent(projectId)}/runtime-settings`)).settings;}
export async function updateProjectRuntimeSettings(projectId:string,input:Partial<ProjectRuntimeSettings>):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/runtime-settings`,'PATCH',input);}
export async function discoverPublishedProjects():Promise<DiscoveredProject[]>{return (await request<{projects:DiscoveredProject[]}>('/api/projects/discover')).projects;}
export async function getProjectPublication(projectId:string):Promise<{project:CloudProject;publication:ProjectPublication}>{return request(`/api/projects/${encodeURIComponent(projectId)}/publication`);}
export async function publishProjectSnapshot(projectId:string,snapshot:Record<string,string>):Promise<{publication:ProjectPublication;visibility:'authenticated_public'}>{return authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/publish`,'POST',{snapshot});}
export async function unpublishProject(projectId:string):Promise<void>{await authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/unpublish`,'POST',{});}
export async function forkPublishedProject(projectId:string,name?:string):Promise<{project:CloudProject;publication:ProjectPublication}>{return authenticatedMutation(`/api/projects/${encodeURIComponent(projectId)}/fork`,'POST',{name});}
export async function getAccountUsage(days=30):Promise<AccountUsageDay[]>{return (await request<{usage:AccountUsageDay[]}>(`/api/user/usage?days=${Math.max(1,Math.min(90,Math.floor(days)))}`)).usage;}


export type CmsListItem = {
  id: number;
  slug: string;
  title: string;
  excerpt?: string;
  category?: string;
  author?: string | null;
  author_avatar_url?: string | null;
  published_at?: string;
  updated_at?: string;
  tags?: string[];
  cover_image_url?: string | null;
  cover_image_alt?: string | null;
  cover_image_width?: number | null;
  cover_image_height?: number | null;
  seo_title?: string | null;
  seo_description?: string | null;
  noindex?: boolean | number;
  deleted_at?: string | null;
};
export type InteractiveContentBlock = { id: string; type: string; data: Record<string, unknown> };
export type InteractiveContentDocument = { schemaVersion: 1; time: number; blocks: InteractiveContentBlock[] };
export type CmsContentFormat = 'markdown' | 'blocks';
export type CmsDetail = CmsListItem & { content_html: string; content_markdown?: string; content_format?: CmsContentFormat; content_document?: InteractiveContentDocument | null; next?: { slug: string; title: string } | null; previous?: { slug: string; title: string } | null };
export type AdminCmsListItem = CmsListItem & { status: 'draft' | 'published'; created_at?: string; sort_order?: number; deleted_at?: string | null; seo_title?: string | null; seo_description?: string | null; noindex?: boolean | number };
export type AdminCmsDetail = AdminCmsListItem & { content_markdown?: string; content_format?: CmsContentFormat; content_schema_version?: number; content_document: InteractiveContentDocument; edit_version?: number; migration_warnings?: string[] };
export type CmsInteractiveKind = 'blog' | 'docs';
export type CmsRuntimeResult = { status: string; exitCode: number; stdout: string; stderr: string; projectModeId?: string; entry?: string };
export type CmsCodeExerciseCheckResult = CmsRuntimeResult & { passed: boolean; visibleTests: Array<{ index: number; label: string; passed: boolean; exitCode: number; stdout: string; stderr: string }>; hiddenTests: { passed: number; total: number } };
export type CmsTerminalTaskStartResult = { attempt: { id: string; attemptNumber: number; projectId: string; generation: number }; terminalSession: unknown };
export type CmsInteractiveState = { blocks: Record<string,{type?:string;status?:string;state?:Record<string,unknown>;updatedAt?:string|null;terminalAttempt?:{id:string;attemptNumber:number;status:string;createdAt?:string|null;completedAt?:string|null}}> };
export type CmsRevision = { id:number; edit_version?:number; editVersion?:number; title:string; slug:string; status:'draft'|'published'; created_at?:string };
export type CmsMediaAsset = { id:number; content_kind?:CmsInteractiveKind; media_kind?:string; mediaKind?:string; url:string; original_name?:string; originalName?:string; mime_type?:string; mimeType?:string; size_bytes?:number; sizeBytes?:number; width?:number|null; height?:number|null; created_at?:string };

export type BlogTagSummary = { name: string; slug: string; count: number | string };
export type BlogListPage = { items: CmsListItem[]; page: number; pageSize: number; total: number; tags: BlogTagSummary[] };

export async function listBlogPage(input: { search?: string; tag?: string; page?: number; pageSize?: number } = {}): Promise<BlogListPage> {
  const query = new URLSearchParams();
  if (input.search?.trim()) query.set('search', input.search.trim());
  if (input.tag?.trim()) query.set('tag', input.tag.trim());
  query.set('page', String(input.page ?? 1));
  query.set('pageSize', String(input.pageSize ?? 24));
  return request<BlogListPage>(`/api/blog?${query.toString()}`);
}

export async function listBlog(): Promise<CmsListItem[]> {
  return (await listBlogPage({ pageSize: 100 })).items;
}
export async function getBlog(slug: string): Promise<CmsDetail> {
  return (await request<{ item: CmsDetail }>(`/api/blog/${encodeURIComponent(slug)}`)).item;
}
export async function listDocs(): Promise<CmsListItem[]> {
  return (await request<{ items: CmsListItem[] }>('/api/docs')).items;
}
export async function getDoc(slug: string): Promise<CmsDetail> {
  return (await request<{ item: CmsDetail }>(`/api/docs/${encodeURIComponent(slug)}`)).item;
}


export type LegalPage = {
  slug: 'privacy' | 'terms';
  title: string;
  content_markdown?: string;
  content_html?: string;
  created_at?: string | null;
  updated_at?: string | null;
};

export async function getLegalPage(slug: 'privacy' | 'terms'): Promise<LegalPage> {
  return (await request<{ page: LegalPage }>(`/api/legal/${slug}`)).page;
}

export type ChangeLogPageSettings = {
  title: string;
  intro_markdown?: string;
  intro_html?: string;
  created_at?: string | null;
  updated_at?: string | null;
};

export type ChangeLogEvent = {
  id: number;
  title: string;
  content_markdown?: string;
  content_html?: string;
  change_date: string;
  status?: 'draft' | 'published';
  published_at?: string | null;
  created_at?: string | null;
  updated_at?: string | null;
};

export type PublicChangeLog = {
  page: ChangeLogPageSettings;
  items: ChangeLogEvent[];
};

export async function getChangeLog(): Promise<PublicChangeLog> {
  return request<PublicChangeLog>('/api/change-log');
}

export async function submitContact(input: { name: string; email: string; subject: string; message: string; website?: string }): Promise<{ ticketId: number; emailNotification: boolean }> {
  return request('/api/contact', { method: 'POST', body: JSON.stringify(input), headers: { 'Content-Type': 'application/json' } });
}

export async function adminLogin(email: string, password: string): Promise<{ csrfToken: string; forcePasswordChange: boolean }> {
  return request('/api/admin/login', { method: 'POST', body: JSON.stringify({ email, password }), headers: { 'Content-Type': 'application/json' } });
}
export async function adminChangePassword(password: string): Promise<void> {
  await authenticatedMutation('/api/admin/change-password', 'POST', { password });
}
export async function adminGet<T>(path: string): Promise<T> { return request<T>(path); }
export async function adminMutate<T>(path: string, method: 'POST' | 'PATCH' | 'DELETE', body?: unknown): Promise<T> {
  return authenticatedMutation<T>(path, method, body);
}


export async function adminUploadBlogCover(file: File): Promise<{ url: string; width: number; height: number }> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, file.name || 'cover-image');
  const response = await fetch('/api/admin/blog/upload-cover', {
    method: 'POST',
    credentials: 'same-origin',
    cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken },
    body,
  });
  return parseResponse<{ url: string; width: number; height: number }>(response);
}

export type CmsMediaUploadResult = { url: string; originalName?: string; mimeType?: string; sizeBytes?: number; width?: number | null; height?: number | null };
export async function adminUploadCmsMedia(kind: CmsInteractiveKind, file: Blob, filename = 'cms-image.png', mediaKind: 'image' | 'video' | 'caption' | 'audio' | 'file' = 'image'): Promise<CmsMediaUploadResult> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, filename);
  const response = await fetch(`/api/admin/cms/upload-media/${kind}/${mediaKind}`, {
    method: 'POST', credentials: 'same-origin', cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken }, body,
  });
  return parseResponse<CmsMediaUploadResult>(response);
}
export async function adminUploadCmsMediaWithProgress(kind: CmsInteractiveKind, mediaKind: 'image' | 'video' | 'caption' | 'audio' | 'file', file: File, onProgress: (progress: { loaded: number; total: number; percent: number }) => void, signal?: AbortSignal): Promise<CmsMediaUploadResult> {
  const auth = await getAuthState(); if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  return new Promise((resolve,reject)=>{
    const xhr=new XMLHttpRequest(); const form=new FormData(); form.set('file',file); xhr.open('POST',`/api/admin/cms/upload-media/${kind}/${mediaKind}`); xhr.withCredentials=true; xhr.setRequestHeader('Accept','application/json'); xhr.setRequestHeader('x-learnitive-csrf',auth.csrfToken);
    xhr.upload.addEventListener('progress',event=>{const total=event.lengthComputable?event.total:file.size;const loaded=Math.min(event.loaded,total||event.loaded);onProgress({loaded,total,percent:total>0?Math.min(100,Math.round(loaded/total*100)):0})});
    xhr.addEventListener('load',()=>{let payload:Record<string,unknown>={};try{payload=JSON.parse(xhr.responseText||'{}')}catch{/* handled below */}if(xhr.status>=200&&xhr.status<300&&typeof payload.url==='string'){onProgress({loaded:file.size,total:file.size,percent:100});resolve(payload as CmsMediaUploadResult);return}reject(new SaasApiError(xhr.status||500,String(payload.error||'cms_media_upload_failed')))});
    xhr.addEventListener('error',()=>reject(new SaasApiError(xhr.status||0,'cms_media_upload_network_error')));xhr.addEventListener('abort',()=>reject(new SaasApiError(0,'cms_media_upload_cancelled')));if(signal){if(signal.aborted){xhr.abort();return}signal.addEventListener('abort',()=>xhr.abort(),{once:true})}xhr.send(form);
  });
}
export async function adminPreviewMarkdown(contentMarkdown: string): Promise<string> {
  const result = await authenticatedMutation<{ contentHtml: string }>('/api/admin/cms/preview','POST',{ contentMarkdown });
  return result.contentHtml;
}
export async function adminPreviewInteractiveContent(contentDocument: InteractiveContentDocument): Promise<{ contentHtml: string; contentDocument: InteractiveContentDocument }> {
  return authenticatedMutation('/api/admin/cms/preview','POST',{ contentDocument });
}

export async function adminCmsPreviewRuntime(kind:CmsInteractiveKind,contentDocument:InteractiveContentDocument,blockId:string,action:string,input:Record<string,unknown>={}):Promise<Record<string,unknown>>{return authenticatedMutation('/api/admin/cms/preview/runtime','POST',{kind,contentDocument,blockId,action,...input});}
export async function adminListCmsMedia(kind:CmsInteractiveKind,mediaKind=''):Promise<CmsMediaAsset[]>{const query=mediaKind?`?kind=${encodeURIComponent(mediaKind)}`:'';return (await adminGet<{items:CmsMediaAsset[]}>(`/api/admin/cms/media/${kind}${query}`)).items;}
export async function adminDeleteCmsMedia(kind:CmsInteractiveKind,id:number):Promise<void>{await adminMutate(`/api/admin/cms/media/${kind}/${id}`,'DELETE');}
export async function adminListCmsRevisions(kind:CmsInteractiveKind,id:number):Promise<CmsRevision[]>{return (await adminGet<{items:CmsRevision[]}>(`/api/admin/${kind}/${id}/revisions`)).items;}
export async function adminCreateCmsRevision(kind:CmsInteractiveKind,id:number):Promise<CmsRevision>{return (await adminMutate<{item:CmsRevision}>(`/api/admin/${kind}/${id}/revisions`,'POST',{})).item;}
export async function adminRestoreCmsRevision(kind:CmsInteractiveKind,id:number,revisionId:number):Promise<{editVersion?:number}>{return (await adminMutate<{item:{editVersion?:number}}>(`/api/admin/${kind}/${id}/revisions/${revisionId}`,'POST',{})).item;}
export async function adminRestoreCmsContent(kind:CmsInteractiveKind,id:number):Promise<void>{await adminMutate(`/api/admin/${kind}/${id}/restore`,'POST',{});}
export async function adminPurgeCmsContent(kind:CmsInteractiveKind,id:number):Promise<void>{await adminMutate(`/api/admin/${kind}/${id}/purge`,'DELETE');}
export async function cmsGetInteractiveState(kind:CmsInteractiveKind,slug:string):Promise<CmsInteractiveState>{return (await request<{state:CmsInteractiveState}>(`/api/${kind}/${encodeURIComponent(slug)}/interactive-state`)).state;}
export async function cmsRunCode(kind:CmsInteractiveKind,slug:string,blockId:string,code:string,stdin=''):Promise<CmsRuntimeResult>{return (await authenticatedMutation<{result:CmsRuntimeResult}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/run-code`,'POST',{code,stdin})).result;}
export async function cmsRunCodeExercise(kind:CmsInteractiveKind,slug:string,blockId:string,files:Record<string,string>,entryFile:string,stdin=''):Promise<CmsRuntimeResult>{return (await authenticatedMutation<{result:CmsRuntimeResult}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/run`,'POST',{files,entryFile,stdin})).result;}
export async function cmsCheckCodeExercise(kind:CmsInteractiveKind,slug:string,blockId:string,files:Record<string,string>,entryFile:string):Promise<CmsCodeExerciseCheckResult>{return (await authenticatedMutation<{result:CmsCodeExerciseCheckResult}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/check`,'POST',{files,entryFile})).result;}
export async function cmsGradeCheckpoint(kind:CmsInteractiveKind,slug:string,blockId:string,answer:number|number[]):Promise<{correct:boolean;explanation?:string}>{return (await authenticatedMutation<{result:{correct:boolean;explanation?:string}}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/checkpoint`,'POST',{answer})).result;}
export async function cmsGetSolution(kind:CmsInteractiveKind,slug:string,blockId:string):Promise<{title:string;content:string}>{return (await request<{solution:{title:string;content:string}}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/solution`)).solution;}
export async function cmsStartTerminalTask(kind:CmsInteractiveKind,slug:string,blockId:string):Promise<CmsTerminalTaskStartResult>{return authenticatedMutation(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/terminal-task/start`,'POST',{});}
export async function cmsVerifyTerminalTask(kind:CmsInteractiveKind,slug:string,blockId:string,attemptId:string):Promise<{passed:boolean;message?:string;checks?:Array<{label:string;passed:boolean}>}>{return (await authenticatedMutation<{result:{passed:boolean;message?:string;checks?:Array<{label:string;passed:boolean}>}}>(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/terminal-task/${encodeURIComponent(attemptId)}/verify`,'POST',{})).result;}
export async function cmsResetTerminalTask(kind:CmsInteractiveKind,slug:string,blockId:string,attemptId:string):Promise<void>{await authenticatedMutation(`/api/${kind}/${encodeURIComponent(slug)}/blocks/${encodeURIComponent(blockId)}/terminal-task/${encodeURIComponent(attemptId)}/reset`,'POST',{});}

export type AdminTemplateRecord = {
  id: string;
  slug: string;
  tier: 'free' | 'pro';
  name: string;
  shortTitle?: string;
  description?: string;
  longDescription?: string;
  status: 'draft' | 'published' | 'archived';
  schemaVersion?: number;
  projectModeId: string;
  projectType: 'web' | 'code' | 'document';
  primaryLanguageId: string;
  primaryLanguage: string;
  frameworkId?: string;
  framework?: string;
  useCaseId: string;
  buildOutput?: string;
  sourceType?: string;
  validationStatus: 'draft' | 'validating' | 'valid' | 'invalid' | 'needs-revalidation';
  validationRevision?: string;
  validationTimestamp?: string;
  validationError?: string;
  validationRuntimeVersion?: string;
  category?: string;
  categoryId?: string;
  institution?: string;
  country?: string;
  countryCode?: string;
  region?: string;
  degreeLevels?: string[];
  faculty?: string;
  department?: string;
  journal?: string;
  publisher?: string;
  conference?: string;
  provenance?: 'official' | 'adapted' | 'community' | 'learnitive';
  sourceUrl?: string;
  originalAuthor?: string;
  author?: string;
  license?: string;
  attribution?: string;
  entryFile?: string;
  compiler?: string;
  bibliographyEngine?: string;
  documentClass?: string;
  requiredPackages?: string[];
  tags?: string[];
  features?: string[];
  version?: string;
  createdAt?: string;
  updatedAt?: string;
  lastVerified?: string;
  thumbnail?: string;
  preview?: string;
  previewResolved?: string;
  previewKind?: 'image' | 'pdf' | null;
  previewAutomatic?: boolean;
  seoTitle?: string;
  seoDescription?: string;
  noIndex?: boolean;
  modifiedAt?: string;
};

export type AdminTemplateCategoryRecord = {
  id: string;
  slug: string;
  name: string;
  description?: string;
  icon: string;
  sortOrder: number;
  status: 'draft' | 'published' | 'archived';
  provenance: 'official' | 'adapted' | 'community' | 'learnitive';
  createdAt: string;
  updatedAt: string;
};

export type AdminInstitutionRecord = {
  id: string;
  slug: string;
  name: string;
  displayName: string;
  shortName?: string;
  country?: string;
  countryCode?: string;
  region?: string;
  website?: string;
  logo?: string;
  description?: string;
  seoDescription?: string;
  status: 'draft' | 'published' | 'archived';
  aliases?: string[];
};

async function adminUploadTemplateFile(path: string, file: File): Promise<{ item: AdminTemplateRecord; sync?: Record<string, unknown> }> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, file.name || 'template.zip');
  const response = await fetch(path, {
    method: 'POST',
    credentials: 'same-origin',
    cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken },
    body,
  });
  return parseResponse(response);
}

export async function adminUploadTemplate(
  file: File,
  tier: 'free' | 'pro',
  slug?: string,
  name?: string,
  projectModeId?: string,
  useCaseId?: string,
): Promise<{ item: AdminTemplateRecord }> {
  const query = new URLSearchParams({ tier });
  if (slug?.trim()) query.set('slug', slug.trim());
  if (name?.trim()) query.set('name', name.trim());
  if (projectModeId?.trim()) query.set('projectModeId', projectModeId.trim());
  if (useCaseId?.trim()) query.set('useCaseId', useCaseId.trim());
  return adminUploadTemplateFile(`/api/admin/templates/upload?${query.toString()}`, file);
}

export async function adminReplaceTemplate(slug: string, file: File): Promise<{ item: AdminTemplateRecord; sync?: Record<string, unknown> }> {
  return adminUploadTemplateFile(`/api/admin/templates/${encodeURIComponent(slug)}/replace`, file);
}


export async function adminUploadTemplatePreview(slug: string, file: File): Promise<{ item: AdminTemplateRecord; kind: 'image' | 'pdf'; mime: string; bytes: number; sync?: Record<string, unknown> }> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, file.name || 'preview');
  const response = await fetch(`/api/admin/templates/${encodeURIComponent(slug)}/preview`, {
    method: 'POST',
    credentials: 'same-origin',
    cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken },
    body,
  });
  return parseResponse(response);
}

export async function adminClearTemplatePreview(slug: string): Promise<{ item: AdminTemplateRecord; sync?: Record<string, unknown> }> {
  return authenticatedMutation(`/api/admin/templates/${encodeURIComponent(slug)}/preview`, 'DELETE', {});
}

export async function adminUploadTemplateThumbnail(slug: string, file: File): Promise<{ item: AdminTemplateRecord; mime: string; bytes: number; sync?: Record<string, unknown> }> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, file.name || 'thumbnail');
  const response = await fetch(`/api/admin/templates/${encodeURIComponent(slug)}/thumbnail`, {
    method: 'POST',
    credentials: 'same-origin',
    cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken },
    body,
  });
  return parseResponse(response);
}

export async function adminClearTemplateThumbnail(slug: string): Promise<{ item: AdminTemplateRecord; sync?: Record<string, unknown> }> {
  return authenticatedMutation(`/api/admin/templates/${encodeURIComponent(slug)}/thumbnail`, 'DELETE', {});
}

export async function adminUploadInstitutionLogo(id: string, file: File): Promise<{ institution: AdminInstitutionRecord; mime: string; bytes: number; sync?: Record<string, unknown> }> {
  const auth = await getAuthState();
  if (!auth.authenticated) throw new SaasApiError(401, 'authentication_required');
  const body = new FormData();
  body.append('file', file, file.name || 'logo');
  const response = await fetch(`/api/admin/institutions/${encodeURIComponent(id)}/logo`, {
    method: 'POST', credentials: 'same-origin', cache: 'no-store',
    headers: { Accept: 'application/json', 'x-learnitive-csrf': auth.csrfToken }, body,
  });
  return parseResponse(response);
}

export async function adminClearInstitutionLogo(id: string): Promise<{ institution: AdminInstitutionRecord; sync?: Record<string, unknown> }> {
  return authenticatedMutation(`/api/admin/institutions/${encodeURIComponent(id)}/logo`, 'DELETE', {});
}
