import { SpiceDbRelations, SpiceDbResourceTypes } from "./types.js";
import { fromSpiceDbProjectId, toSpiceDbProjectId } from "./config.js";
import { RelationshipOperation, checkBulkPermissions, deleteRelationship, getSpiceClient, readRelationships, writeRelationship } from "./client.js";

//#region src/auth/authz/sync.ts
/**
* SpiceDB Sync Utilities
*
* Functions for syncing data between better-auth and SpiceDB.
*/
/**
* Sync a user's org membership to SpiceDB.
* Call when: user joins org, role changes, user leaves org.
*/
async function syncOrgMemberToSpiceDb(params) {
	if (params.action === "add") await writeRelationship({
		resourceType: SpiceDbResourceTypes.ORGANIZATION,
		resourceId: params.tenantId,
		relation: params.role,
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.userId
	});
	else await deleteRelationship({
		resourceType: SpiceDbResourceTypes.ORGANIZATION,
		resourceId: params.tenantId,
		relation: params.role,
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.userId
	});
}
/**
* Change a user's organization role.
* Removes the old role and adds the new one atomically in a single transaction.
* Call when: user's org role is updated (e.g., member -> admin).
*/
async function changeOrgRole(params) {
	if (params.oldRole === params.newRole) return;
	await getSpiceClient().promises.writeRelationships({
		updates: [{
			operation: RelationshipOperation.DELETE,
			relationship: {
				resource: {
					objectType: SpiceDbResourceTypes.ORGANIZATION,
					objectId: params.tenantId
				},
				relation: params.oldRole,
				subject: {
					object: {
						objectType: SpiceDbResourceTypes.USER,
						objectId: params.userId
					},
					optionalRelation: ""
				},
				optionalCaveat: void 0
			}
		}, {
			operation: RelationshipOperation.TOUCH,
			relationship: {
				resource: {
					objectType: SpiceDbResourceTypes.ORGANIZATION,
					objectId: params.tenantId
				},
				relation: params.newRole,
				subject: {
					object: {
						objectType: SpiceDbResourceTypes.USER,
						objectId: params.userId
					},
					optionalRelation: ""
				},
				optionalCaveat: void 0
			}
		}],
		optionalPreconditions: [],
		optionalTransactionMetadata: void 0
	});
}
/**
* Sync a new project to SpiceDB.
* Links project to org and grants creator project_admin role (if not already org admin/owner).
* Call when: project is created.
*/
async function syncProjectToSpiceDb(params) {
	const spice = getSpiceClient();
	const isOrgAdminOrOwner = (await checkBulkPermissions({
		resourceType: SpiceDbResourceTypes.ORGANIZATION,
		resourceId: params.tenantId,
		permissions: ["manage"],
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.creatorUserId
	})).manage;
	const spiceProjectId = toSpiceDbProjectId(params.tenantId, params.projectId);
	const updates = [{
		operation: RelationshipOperation.TOUCH,
		relationship: {
			resource: {
				objectType: SpiceDbResourceTypes.PROJECT,
				objectId: spiceProjectId
			},
			relation: SpiceDbRelations.ORGANIZATION,
			subject: {
				object: {
					objectType: SpiceDbResourceTypes.ORGANIZATION,
					objectId: params.tenantId
				},
				optionalRelation: ""
			},
			optionalCaveat: void 0
		}
	}];
	if (!isOrgAdminOrOwner) updates.push({
		operation: RelationshipOperation.TOUCH,
		relationship: {
			resource: {
				objectType: SpiceDbResourceTypes.PROJECT,
				objectId: spiceProjectId
			},
			relation: SpiceDbRelations.PROJECT_ADMIN,
			subject: {
				object: {
					objectType: SpiceDbResourceTypes.USER,
					objectId: params.creatorUserId
				},
				optionalRelation: ""
			},
			optionalCaveat: void 0
		}
	});
	await spice.promises.writeRelationships({
		updates,
		optionalPreconditions: [],
		optionalTransactionMetadata: void 0
	});
}
/**
* Grant project access to a user.
*/
async function grantProjectAccess(params) {
	await writeRelationship({
		resourceType: SpiceDbResourceTypes.PROJECT,
		resourceId: toSpiceDbProjectId(params.tenantId, params.projectId),
		relation: params.role,
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.userId
	});
}
/**
* Revoke project access from a user.
*/
async function revokeProjectAccess(params) {
	await deleteRelationship({
		resourceType: SpiceDbResourceTypes.PROJECT,
		resourceId: toSpiceDbProjectId(params.tenantId, params.projectId),
		relation: params.role,
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.userId
	});
}
/**
* Change a user's project role.
* Removes the old role and adds the new one atomically in a single transaction.
*/
async function changeProjectRole(params) {
	if (params.oldRole === params.newRole) return;
	const spice = getSpiceClient();
	const spiceProjectId = toSpiceDbProjectId(params.tenantId, params.projectId);
	await spice.promises.writeRelationships({
		updates: [{
			operation: RelationshipOperation.DELETE,
			relationship: {
				resource: {
					objectType: SpiceDbResourceTypes.PROJECT,
					objectId: spiceProjectId
				},
				relation: params.oldRole,
				subject: {
					object: {
						objectType: SpiceDbResourceTypes.USER,
						objectId: params.userId
					},
					optionalRelation: ""
				},
				optionalCaveat: void 0
			}
		}, {
			operation: RelationshipOperation.TOUCH,
			relationship: {
				resource: {
					objectType: SpiceDbResourceTypes.PROJECT,
					objectId: spiceProjectId
				},
				relation: params.newRole,
				subject: {
					object: {
						objectType: SpiceDbResourceTypes.USER,
						objectId: params.userId
					},
					optionalRelation: ""
				},
				optionalCaveat: void 0
			}
		}],
		optionalPreconditions: [],
		optionalTransactionMetadata: void 0
	});
}
/**
* Remove a project from SpiceDB.
* Call when: project is deleted.
*/
async function removeProjectFromSpiceDb(params) {
	await getSpiceClient().promises.deleteRelationships({
		relationshipFilter: {
			resourceType: SpiceDbResourceTypes.PROJECT,
			optionalResourceId: toSpiceDbProjectId(params.tenantId, params.projectId),
			optionalResourceIdPrefix: "",
			optionalRelation: ""
		},
		optionalPreconditions: [],
		optionalLimit: 0,
		optionalAllowPartialDeletions: false,
		optionalTransactionMetadata: void 0
	});
}
/**
* List all explicit project members from SpiceDB.
* Returns users with project_admin, project_member, or project_viewer roles.
*
* NOTE: This is an appropriate use of readRelationships for data introspection,
* not permission logic (per SpiceDB best practices).
*/
async function listProjectMembers(params) {
	return (await readRelationships({
		resourceType: SpiceDbResourceTypes.PROJECT,
		resourceId: toSpiceDbProjectId(params.tenantId, params.projectId)
	})).filter((rel) => rel.subjectType === SpiceDbResourceTypes.USER && (rel.relation === SpiceDbRelations.PROJECT_ADMIN || rel.relation === SpiceDbRelations.PROJECT_MEMBER || rel.relation === SpiceDbRelations.PROJECT_VIEWER)).map((rel) => ({
		userId: rel.subjectId,
		role: rel.relation
	}));
}
/**
* List all project memberships for a specific user.
* Returns projects where the user has explicit project_admin, project_member, or project_viewer roles.
*
* NOTE: This is an appropriate use of readRelationships for data introspection,
* not permission logic (per SpiceDB best practices).
*/
async function listUserProjectMembershipsInSpiceDb(params) {
	return (await readRelationships({
		resourceType: SpiceDbResourceTypes.PROJECT,
		subjectType: SpiceDbResourceTypes.USER,
		subjectId: params.userId
	})).filter((rel) => rel.relation === SpiceDbRelations.PROJECT_ADMIN || rel.relation === SpiceDbRelations.PROJECT_MEMBER || rel.relation === SpiceDbRelations.PROJECT_VIEWER).flatMap((rel) => {
		try {
			const parsed = fromSpiceDbProjectId(rel.resourceId);
			if (parsed.tenantId !== params.tenantId) return [];
			return [{
				projectId: parsed.projectId,
				role: rel.relation
			}];
		} catch {
			return [];
		}
	});
}
/**
* Revoke all project memberships for a user.
* Call when: user is promoted to org admin (they get inherited access, explicit project roles become redundant).
*
* Uses efficient bulk delete - deletes all project relationships for user without listing first.
*/
async function revokeAllProjectMemberships(params) {
	const spice = getSpiceClient();
	const tenantPrefix = `${params.tenantId}/`;
	await Promise.all([
		spice.promises.deleteRelationships({
			relationshipFilter: {
				resourceType: SpiceDbResourceTypes.PROJECT,
				optionalResourceId: "",
				optionalResourceIdPrefix: tenantPrefix,
				optionalRelation: SpiceDbRelations.PROJECT_ADMIN,
				optionalSubjectFilter: {
					subjectType: SpiceDbResourceTypes.USER,
					optionalSubjectId: params.userId,
					optionalRelation: void 0
				}
			},
			optionalPreconditions: [],
			optionalLimit: 0,
			optionalAllowPartialDeletions: false,
			optionalTransactionMetadata: void 0
		}),
		spice.promises.deleteRelationships({
			relationshipFilter: {
				resourceType: SpiceDbResourceTypes.PROJECT,
				optionalResourceId: "",
				optionalResourceIdPrefix: tenantPrefix,
				optionalRelation: SpiceDbRelations.PROJECT_MEMBER,
				optionalSubjectFilter: {
					subjectType: SpiceDbResourceTypes.USER,
					optionalSubjectId: params.userId,
					optionalRelation: void 0
				}
			},
			optionalPreconditions: [],
			optionalLimit: 0,
			optionalAllowPartialDeletions: false,
			optionalTransactionMetadata: void 0
		}),
		spice.promises.deleteRelationships({
			relationshipFilter: {
				resourceType: SpiceDbResourceTypes.PROJECT,
				optionalResourceId: "",
				optionalResourceIdPrefix: tenantPrefix,
				optionalRelation: SpiceDbRelations.PROJECT_VIEWER,
				optionalSubjectFilter: {
					subjectType: SpiceDbResourceTypes.USER,
					optionalSubjectId: params.userId,
					optionalRelation: void 0
				}
			},
			optionalPreconditions: [],
			optionalLimit: 0,
			optionalAllowPartialDeletions: false,
			optionalTransactionMetadata: void 0
		})
	]);
}
/**
* Revoke ALL user relationships within an organization.
* Call when: user is being removed from an organization.
*
* This removes:
* 1. Organization-level relationships (owner, admin, member)
* 2. All project-level relationships within the organization
*
* Uses the most efficient approach: filters by subject without specifying relation.
* This is much more efficient than individual relation-specific deletions.
*/
async function revokeAllUserRelationships(params) {
	const spice = getSpiceClient();
	const tenantPrefix = `${params.tenantId}/`;
	await Promise.all([spice.promises.deleteRelationships({
		relationshipFilter: {
			resourceType: SpiceDbResourceTypes.ORGANIZATION,
			optionalResourceId: params.tenantId,
			optionalResourceIdPrefix: "",
			optionalRelation: "",
			optionalSubjectFilter: {
				subjectType: SpiceDbResourceTypes.USER,
				optionalSubjectId: params.userId,
				optionalRelation: void 0
			}
		},
		optionalPreconditions: [],
		optionalLimit: 0,
		optionalAllowPartialDeletions: false,
		optionalTransactionMetadata: void 0
	}), spice.promises.deleteRelationships({
		relationshipFilter: {
			resourceType: SpiceDbResourceTypes.PROJECT,
			optionalResourceId: "",
			optionalResourceIdPrefix: tenantPrefix,
			optionalRelation: "",
			optionalSubjectFilter: {
				subjectType: SpiceDbResourceTypes.USER,
				optionalSubjectId: params.userId,
				optionalRelation: void 0
			}
		},
		optionalPreconditions: [],
		optionalLimit: 0,
		optionalAllowPartialDeletions: false,
		optionalTransactionMetadata: void 0
	})]);
}

//#endregion
export { changeOrgRole, changeProjectRole, grantProjectAccess, listProjectMembers, listUserProjectMembershipsInSpiceDb, removeProjectFromSpiceDb, revokeAllProjectMemberships, revokeAllUserRelationships, revokeProjectAccess, syncOrgMemberToSpiceDb, syncProjectToSpiceDb };