/**
 * Thin API client for the org admin backend. Same-origin cookie auth.
 */

export class ApiError extends Error {
	code: string;
	status: number;
	constructor(code: string, message: string, status: number) {
		super(message);
		this.name = 'ApiError';
		this.code = code;
		this.status = status;
	}
}

async function request(method: string, url: string, body?: unknown): Promise<unknown> {
	const init: RequestInit = {
		method,
		credentials: 'same-origin',
		headers: body !== undefined ? { 'content-type': 'application/json' } : undefined,
		body: body !== undefined ? JSON.stringify(body) : undefined,
	};
	const res = await fetch(url, init);
	const text = await res.text();
	let data: unknown = null;
	if (text !== '') {
		try {
			data = JSON.parse(text);
		} catch {
			data = text;
		}
	}
	if (!res.ok) {
		const err = (data as { error?: { code?: string; message?: string } } | null)?.error;
		throw new ApiError(err?.code ?? 'http_error', err?.message ?? `HTTP ${res.status}`, res.status);
	}
	return data;
}

const get = (u: string) => request('GET', u);
const post = (u: string, b?: unknown) => request('POST', u, b);
const put = (u: string, b?: unknown) => request('PUT', u, b);
const patch = (u: string, b?: unknown) => request('PATCH', u, b);
const del = (u: string) => request('DELETE', u);

const orgBase = (slug: string) => `/api/org/${encodeURIComponent(slug)}`;

// --- Types ---

export interface OrgMembership {
	id: string;
	slug: string;
	name: string;
	status: string;
	role: 'OWNER' | 'ADMIN' | 'MEMBER';
}

export interface MeResponse {
	user: {
		id: string;
		feishuOpenId: string;
		displayName: string;
		avatarUrl: string | null;
	};
	organizations: OrgMembership[];
}

export interface OrgMember {
	userId: string;
	feishuOpenId: string;
	displayName: string;
	avatarUrl: string | null;
	role: 'OWNER' | 'ADMIN' | 'MEMBER';
	createdAt: string;
}

export interface TeamRow {
	id: string;
	slug: string;
	name: string;
	description: string | null;
	memberCount: number;
	createdAt: string;
}

export interface TeamMemberRow {
	userId: string;
	feishuOpenId: string;
	displayName: string;
	createdAt: string;
}

export interface ExplorerFolder {
	id: string;
	name: string;
	parentId: string | null;
	sortKey: string;
	projectCount: number;
	childFolderCount: number;
}

export interface ExplorerProject {
	id: string;
	name: string;
	folderId: string | null;
	createdAt: string;
	binding: { chatId: string; createdAt: string } | null;
}

export interface ExplorerData {
	folders: ExplorerFolder[];
	projects: ExplorerProject[];
}

export interface ProjectDetail {
	id: string;
	name: string;
	folderId: string | null;
	folder: { id: string; name: string } | null;
	workspaceDir: string;
	createdAt: string;
	archivedAt: string | null;
	createdBy: { id: string; displayName: string; feishuOpenId: string } | null;
	binding: { chatId: string; createdAt: string } | null;
	actorIsOrgAdmin?: boolean;
	actorCanManageProject?: boolean;
}

export interface TeamAccessEntry {
	grantId: string;
	projectId: string;
	organizationId: string;
	teamId: string;
	teamSlug: string;
	teamName: string;
	role: 'READ' | 'EDIT' | 'MANAGE';
}

export interface SessionSummary {
	id: string;
	provider: string;
	roleId: string;
	model: string;
	title: string | null;
	runCount: number;
	createdAt: string;
	updatedAt: string;
}

export interface ProviderConnectionRow {
	id: string;
	providerId: string;
	mode: 'BYOK' | 'PLATFORM_MANAGED';
	status: 'DRAFT' | 'ACTIVE' | 'DISABLED';
	activeVersion: number | null;
	keyId: string | null;
	createdAt: string;
	updatedAt: string;
}

export interface FeishuApplicationConnection {
	id: string;
	appFingerprint: string;
	status: 'DRAFT' | 'ACTIVE' | 'DISABLED';
	activeVersion: number | null;
	keyId: string | null;
	createdAt: string;
	updatedAt: string;
}

export interface CapabilityConnection {
	id: string;
	capabilityId: string;
	status: 'DRAFT' | 'ACTIVE' | 'DISABLED';
	activeVersion: number | null;
	keyId: string | null;
	createdAt: string;
	updatedAt: string;
}

export interface UsageTotals {
	runCount: number;
	runsWithCost: number;
	runsWithoutCost: number;
	inputTokens: number;
	outputTokens: number;
	costUsd: number | null;
}

export interface ProjectUsageRow extends UsageTotals {
	projectId: string;
	projectName: string;
	folderId: string | null;
}

/** Ledger slice from UsageFact (ADR-0026): separates model tokens vs external meters. */
export interface UsageBreakdownRow {
	kind: string;
	provider: string;
	model: string | null;
	capabilityId: string | null;
	unit: string | null;
	factCount: number;
	factsWithCost: number;
	factsWithoutCost: number;
	inputTokens: number;
	outputTokens: number;
	quantity: number | null;
	costUsd: number | null;
}

export interface UsageReport {
	from: string | null;
	to: string | null;
	projects: ProjectUsageRow[];
	totals: UsageTotals;
	breakdown: UsageBreakdownRow[];
}

export interface ProjectUsageReport extends ProjectUsageRow {
	from: string | null;
	to: string | null;
	breakdown: UsageBreakdownRow[];
}

export interface UsageFactRow {
	id: string;
	occurredAt: string;
	kind: string;
	provider: string;
	model: string | null;
	inputTokens: number | null;
	outputTokens: number | null;
	quantity: number | null;
	unit: string | null;
	costUsd: number | null;
	costSource: string;
	capabilityId: string | null;
	correlationId: string | null;
}

export interface SessionRunRow {
	id: string;
	status: string;
	model: string;
	provider: string;
	inputTokens: number | null;
	outputTokens: number | null;
	costUsd: number | null;
	costSource: string | null;
	startedAt: string;
	finishedAt: string | null;
	error: string | null;
	usageFacts: UsageFactRow[];
}

export interface SessionDetail {
	id: string;
	provider: string;
	roleId: string;
	model: string;
	title: string | null;
	createdAt: string;
	updatedAt: string;
	archivedAt: string | null;
	project: { id: string; name: string };
	runs: SessionRunRow[];
}


export type CapacityDimension =
	| 'requestRate'
	| 'requestBodySize'
	| 'agentConcurrency'
	| 'admissionQueueLength'
	| 'admissionQueueWait'
	| 'fileSize'
	| 'attachmentCount'
	| 'archiveExpansion'
	| 'projectStorage'
	| 'organizationStorage'
	| 'memberCount'
	| 'projectCount'
	| 'teamCount'
	| 'folderCount'
	| 'sessionCount'
	| 'runWallTime'
	| 'runTurns'
	| 'runToolCalls'
	| 'toolWallTime'
	| 'runOutputSize'
	| 'processMemory'
	| 'processCpu'
	| 'processCount';

export interface CapacityDimensionRow {
	dimension: CapacityDimension;
	platformCeiling: number | null;
	organizationLimit: number | null;
	effective: number | null;
}

export interface CapacityPolicyView {
	dimensions: CapacityDimensionRow[];
}

export interface AgentRoleRow {
	id: string;
	roleId: string;
	label: string;
	defaultModel: string | null;
	systemPrompt: string | null;
	tools: readonly string[] | null;
	sortOrder: number;
	isDefault: boolean;
	disabledAt: string | null;
	createdAt: string;
	updatedAt: string;
	skillNames: readonly string[];
}

export interface AgentSkillRow {
	id: string;
	name: string;
	version: string;
	description: string | null;
	contentDigest: string;
	disabledAt: string | null;
	createdAt: string;
	updatedAt: string;
	boundRoleIds: readonly string[];
}

export interface SkillFileEntry {
	path: string;
	content: string;
}

export interface InstalledSkillResult {
	id: string;
	name: string;
	contentDigest: string;
}

export interface AgentModelRow {
	id: string;
	label: string;
	toolCapable: boolean;
}

// --- API ---

export const api = {
	me: () => get('/api/me') as Promise<MeResponse>,
	logout: () => post('/auth/logout'),

	org: (slug: string) =>
		get(orgBase(slug)) as Promise<{
			organization: { id: string; slug: string; name: string; status: string };
			actorRole: string;
		}>,
	settings: (slug: string) => get(`${orgBase(slug)}/settings`) as Promise<{ membersCanCreateProjects: boolean }>,
	setSettings: (slug: string, body: { membersCanCreateProjects: boolean }) =>
		patch(`${orgBase(slug)}/settings`, body) as Promise<{ membersCanCreateProjects: boolean }>,

	members: (slug: string) => get(`${orgBase(slug)}/members`) as Promise<{ members: OrgMember[] }>,
	addMember: (slug: string, body: { feishuOpenId: string; displayName?: string; role: string }) =>
		post(`${orgBase(slug)}/members`, body) as Promise<OrgMember>,
	setMemberRole: (slug: string, userId: string, role: string) => patch(`${orgBase(slug)}/members/${userId}`, { role }),
	revokeMember: (slug: string, userId: string) => post(`${orgBase(slug)}/members/${userId}/revoke`),

	teams: (slug: string) => get(`${orgBase(slug)}/teams`) as Promise<{ teams: TeamRow[] }>,
	createTeam: (slug: string, body: { slug: string; name: string; description?: string }) =>
		post(`${orgBase(slug)}/teams`, body) as Promise<TeamRow>,
	updateTeam: (slug: string, teamId: string, body: { name?: string; description?: string | null }) =>
		patch(`${orgBase(slug)}/teams/${teamId}`, body) as Promise<TeamRow>,
	archiveTeam: (slug: string, teamId: string) => post(`${orgBase(slug)}/teams/${teamId}/archive`),
	teamMembers: (slug: string, teamId: string) =>
		get(`${orgBase(slug)}/teams/${teamId}/members`) as Promise<{ members: TeamMemberRow[] }>,
	addTeamMember: (slug: string, teamId: string, body: { userId?: string; feishuOpenId?: string }) =>
		post(`${orgBase(slug)}/teams/${teamId}/members`, body) as Promise<TeamMemberRow>,
	revokeTeamMember: (slug: string, teamId: string, userId: string) =>
		post(`${orgBase(slug)}/teams/${teamId}/members/${userId}/revoke`),

	explorer: (slug: string) => get(`${orgBase(slug)}/explorer`) as Promise<ExplorerData>,
	myProjects: (slug: string) => get(`${orgBase(slug)}/my-projects`) as Promise<{ projects: ExplorerProject[] }>,
	createFolder: (slug: string, body: { name: string; parentId?: string; sortKey?: string }) =>
		post(`${orgBase(slug)}/folders`, body) as Promise<{
			id: string;
			name: string;
			parentId: string | null;
			sortKey: string;
		}>,
	renameFolder: (slug: string, folderId: string, body: { name?: string; sortKey?: string; parentId?: string | null }) =>
		patch(`${orgBase(slug)}/folders/${folderId}`, body) as Promise<{
			id: string;
			name: string;
			parentId: string | null;
			sortKey: string;
		}>,
	archiveFolder: (slug: string, folderId: string) =>
		post(`${orgBase(slug)}/folders/${folderId}/archive`) as Promise<{ archived: true; folderId: string }>,
	createProject: (slug: string, body: { name: string; folderId?: string }) =>
		post(`${orgBase(slug)}/projects`, body) as Promise<{ id: string; name: string }>,
	project: (slug: string, projectId: string) => get(`${orgBase(slug)}/projects/${projectId}`) as Promise<ProjectDetail>,
	renameProject: (slug: string, projectId: string, name: string) =>
		patch(`${orgBase(slug)}/projects/${projectId}`, { name }),
	moveProject: (slug: string, projectId: string, folderId: string | null) =>
		patch(`${orgBase(slug)}/projects/${projectId}/folder`, { folderId }),
	archiveProject: (slug: string, projectId: string) => post(`${orgBase(slug)}/projects/${projectId}/archive`),
	archiveBinding: (slug: string, projectId: string) => post(`${orgBase(slug)}/projects/${projectId}/binding/archive`),

	teamAccess: (slug: string, projectId: string) =>
		get(`${orgBase(slug)}/projects/${projectId}/team-access`) as Promise<{ access: TeamAccessEntry[] }>,
	grantTeamAccess: (slug: string, projectId: string, body: { teamId?: string; teamSlug?: string; role: string }) =>
		put(`${orgBase(slug)}/projects/${projectId}/team-access`, body) as Promise<TeamAccessEntry>,
	revokeTeamAccess: (slug: string, projectId: string, teamId: string) =>
		del(`${orgBase(slug)}/projects/${projectId}/team-access/${teamId}`),

	sessions: (slug: string, projectId: string, limit?: number) =>
		get(`${orgBase(slug)}/projects/${projectId}/sessions${limit !== undefined ? `?limit=${limit}` : ''}`) as Promise<{
			sessions: SessionSummary[];
		}>,
	session: (slug: string, sessionId: string) =>
		get(`${orgBase(slug)}/sessions/${encodeURIComponent(sessionId)}`) as Promise<SessionDetail>,
	usage: (slug: string, params?: { from?: string; to?: string; folderId?: string }) => {
		const q = new URLSearchParams();
		if (params?.from) q.set('from', params.from);
		if (params?.to) q.set('to', params.to);
		if (params?.folderId) q.set('folderId', params.folderId);
		const qs = q.toString();
		return get(`${orgBase(slug)}/usage${qs ? `?${qs}` : ''}`) as Promise<UsageReport>;
	},
	projectUsage: (slug: string, projectId: string, params?: { from?: string; to?: string }) => {
		const q = new URLSearchParams();
		if (params?.from) q.set('from', params.from);
		if (params?.to) q.set('to', params.to);
		const qs = q.toString();
		return get(
			`${orgBase(slug)}/projects/${encodeURIComponent(projectId)}/usage${qs ? `?${qs}` : ''}`,
		) as Promise<ProjectUsageReport>;
	},


	providerConnections: (slug: string) =>
		get(`${orgBase(slug)}/provider-connections`) as Promise<{ connections: ProviderConnectionRow[] }>,
	rotateProviderConnection: (
		slug: string,
		providerId: string,
		body: { baseUrl: string; authToken: string; anthropicApiKey?: string },
	) =>
		put(
			`${orgBase(slug)}/provider-connections/${encodeURIComponent(providerId)}`,
			body,
		) as Promise<ProviderConnectionRow>,

	feishuApplication: (slug: string) =>
		get(`${orgBase(slug)}/feishu-application-connection`) as Promise<{
			connection: FeishuApplicationConnection | null;
		}>,
	rotateFeishuApplication: (
		slug: string,
		body: {
			appId: string;
			appSecret: string;
			botOpenId: string;
			verificationToken?: string;
			encryptKey?: string;
		},
	) => put(`${orgBase(slug)}/feishu-application-connection`, body) as Promise<FeishuApplicationConnection>,
	disableFeishuApplication: (slug: string) =>
		del(`${orgBase(slug)}/feishu-application-connection`) as Promise<FeishuApplicationConnection>,

	capabilityConnections: (slug: string) =>
		get(`${orgBase(slug)}/capability-connections`) as Promise<{ connections: CapabilityConnection[] }>,
	capabilityConnection: (slug: string, capabilityId: string) =>
		get(`${orgBase(slug)}/capability-connections/${encodeURIComponent(capabilityId)}`) as Promise<{
			connection: CapabilityConnection | null;
		}>,
	rotateCapabilityConnection: (
		slug: string,
		capabilityId: string,
		body: { accessKeyId: string; accessKeySecret: string; endpoint: string },
	) =>
		put(`${orgBase(slug)}/capability-connections/${encodeURIComponent(capabilityId)}`, body) as Promise<CapabilityConnection>,
	disableCapabilityConnection: (slug: string, capabilityId: string) =>
		del(`${orgBase(slug)}/capability-connections/${encodeURIComponent(capabilityId)}`) as Promise<CapabilityConnection>,

	capacityPolicy: (slug: string) => get(`${orgBase(slug)}/capacity-policy`) as Promise<CapacityPolicyView>,
	setCapacityPolicy: (slug: string, body: { limits: Partial<Record<CapacityDimension, number | null>> }) =>
		put(`${orgBase(slug)}/capacity-policy`, body) as Promise<CapacityPolicyView>,

	agentRoles: (slug: string) => get(`${orgBase(slug)}/agent-roles`) as Promise<{ roles: AgentRoleRow[] }>,
	upsertAgentRole: (
		slug: string,
		roleId: string,
		body: {
			label: string;
			defaultModel?: string | null;
			systemPrompt?: string | null;
			tools?: readonly string[] | null;
			sortOrder?: number;
			isDefault?: boolean;
		},
	) => put(`${orgBase(slug)}/agent-roles/${encodeURIComponent(roleId)}`, body) as Promise<AgentRoleRow>,
	setAgentRoleSkills: (slug: string, roleId: string, skillNames: readonly string[]) =>
		put(`${orgBase(slug)}/agent-roles/${encodeURIComponent(roleId)}/skills`, { skillNames }) as Promise<{
			skillNames: string[];
		}>,
	agentSkills: (slug: string) => get(`${orgBase(slug)}/agent-skills`) as Promise<{ skills: AgentSkillRow[] }>,
	agentSkillFiles: (slug: string, name: string) =>
		get(`${orgBase(slug)}/agent-skills/${encodeURIComponent(name)}/files`) as Promise<{ files: SkillFileEntry[] }>,
	installAgentSkill: (slug: string, name: string, body: { version: string; files: readonly SkillFileEntry[] }) =>
		put(`${orgBase(slug)}/agent-skills/${encodeURIComponent(name)}`, body) as Promise<InstalledSkillResult>,
	patchAgentSkill: (slug: string, name: string, body: { description?: string; disabled?: boolean }) =>
		patch(`${orgBase(slug)}/agent-skills/${encodeURIComponent(name)}`, body) as Promise<{ disabled?: boolean; updated?: boolean }>,
	agentModels: (slug: string) => get(`${orgBase(slug)}/agent-models`) as Promise<{ models: AgentModelRow[] }>,
};
