syntax = "proto3";

package chatto.admin.v1;

import "buf/validate/validate.proto";
import "chatto/api/v1/pagination.proto";
import "chatto/api/v1/roles.proto";
import "chatto/api/v1/users.proto";
import "chatto/admin/v1/roles.proto";
import "google/protobuf/timestamp.proto";

// User row returned by server-admin member management reads.
//
// The embedded user record carries public identity fields; remaining fields are
// admin/member metadata with visibility controlled by the service.
message AdminMember {
  reserved 1 to 4;
  reserved "id", "login", "display_name", "avatar_url";

  // Explicit role assignments. The implicit everyone role is omitted.
  repeated string roles = 5;
  // Account creation time, when known.
  google.protobuf.Timestamp created_at = 6;
  reserved 7;
  reserved "deleted";
  // Whether the caller may see that the user has a verified email and one exists.
  bool has_verified_email = 8;
  // Verified email addresses visible to the caller.
  repeated string verified_emails = 9;
  // Whether the caller may delete this account.
  bool viewer_can_delete_account = 10;
  // Last self-service username change, when visible and known.
  google.protobuf.Timestamp last_login_change = 11;
  // Public identity fields for this user.
  chatto.api.v1.User user = 12;
}

// Request server-admin member rows.
message ListMembersRequest {
  reserved 2, 3;
  reserved "limit", "offset";

  // Case-insensitive login/display-name search term. Empty disables search.
  string search = 1;
  // Page request. Defaults to 20 results when absent or limit is zero.
  chatto.api.v1.PageRequest page = 4;
}

// Server-admin member rows plus role summaries.
message ListMembersResponse {
  reserved 3, 4;
  reserved "total_count", "has_more";

  // Matching members.
  repeated AdminMember members = 1;
  // Public roles for display-name lookup.
  repeated chatto.api.v1.Role roles = 2;
  // Page metadata.
  chatto.api.v1.PageInfo page = 5;
}

// Request one server-admin member detail record.
message GetMemberRequest {
  oneof target {
    // Target stable user ID.
    string user_id = 1 [(buf.validate.field).string.min_len = 1];
    // Target login identifier.
    string login = 2 [(buf.validate.field).string.min_len = 1];
  }
}

// Server-admin member detail payload.
message GetMemberResponse {
  // Member record.
  AdminMember member = 1;
  // Roles for assignment UI.
  repeated AdminRole roles = 2;
  // Permissions available for per-user overrides.
  repeated string available_permissions = 3;
  // Whether the caller may assign/revoke roles.
  bool viewer_can_assign_roles = 4;
  // Whether the caller may edit role definitions.
  bool viewer_can_manage_roles = 5;
  // Whether the caller may edit direct user permissions.
  bool viewer_can_manage_user_permissions = 6;
}

// Request server-admin member rows for a set of stable user IDs.
message BatchGetMembersRequest {
  // Required target user IDs. Unknown IDs are omitted from the response.
  repeated string user_ids = 1 [(buf.validate.field).repeated = {
    min_items: 1
    max_items: 100
    items: {
      string: {min_len: 1}
    }
  }];
}

// Batch server-admin member response.
message BatchGetMembersResponse {
  // Found members. The server preserves first-seen request order and
  // de-duplicates repeated IDs.
  repeated AdminMember members = 1;
  // Public roles for display-name lookup.
  repeated chatto.api.v1.Role roles = 2;
}

// Request to assign a role to a user.
message AssignRoleRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
  // Role name to assign.
  string role_name = 2 [(buf.validate.field).string.min_len = 1];
}

// Result of assigning a role.
message AssignRoleResponse {
  reserved 1;
  reserved "assigned";

  // Updated admin member row.
  AdminMember member = 2;
}

// Request to revoke a role from a user.
message RevokeRoleRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
  // Role name to revoke.
  string role_name = 2 [(buf.validate.field).string.min_len = 1];
}

// Result of revoking a role.
message RevokeRoleResponse {
  reserved 1;
  reserved "revoked";

  // Updated admin member row.
  AdminMember member = 2;
}

// Request to update a user's identity as a server-admin action.
message UpdateUserRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
  // New display name, when changing it. Empty clears the explicit display
  // name. The server also rejects control and confusing invisible characters.
  optional string display_name = 2 [(buf.validate.field).string.max_len = 32];
  // New login identifier, when changing it. The server accepts ASCII letters,
  // digits, period, underscore, and hyphen, starting with a letter or digit.
  optional string login = 3 [(buf.validate.field).string = {
    min_len: 2
    max_len: 32
  }];
}

// Result of an admin user update.
message UpdateUserResponse {
  // Updated user profile.
  chatto.api.v1.User user = 1;
  // Updated admin member row.
  AdminMember member = 2;
}

// Request to update a user's password as a server-admin action.
message UpdateUserPasswordRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
  // New password. The server applies the same password policy as registration
  // and password reset.
  string password = 2 [(buf.validate.field).string = {
    min_len: 8
    max_len: 128
  }];
}

// Result of an admin password update.
message UpdateUserPasswordResponse {
  // Updated admin member row.
  AdminMember member = 1;
}

// Request to clear a user's self-service username-change cooldown.
message ClearUsernameCooldownRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
}

// Result of clearing a user's username-change cooldown.
message ClearUsernameCooldownResponse {
  // True when the request completed.
  bool cleared = 1;
}

// Request to delete a user account as a server-admin action.
message DeleteUserRequest {
  // Target user ID.
  string user_id = 1 [(buf.validate.field).string.min_len = 1];
  // Current password proof for accounts with a password when the active
  // runtime credential is no longer fresh.
  string current_password = 2;
}

// Result of deleting a user account.
message DeleteUserResponse {
  // True when the user was deleted.
  bool deleted = 1;
}

// Server-admin user management commands and member-detail reads.
service AdminUserService {
  // Lists server members for the admin members screen. Requires
  // admin.view-users.
  rpc ListMembers(ListMembersRequest) returns (ListMembersResponse);
  // Gets one server member plus role/permission metadata for admin details.
  // Requires admin.view-users. Returns NOT_FOUND when the user does not exist.
  rpc GetMember(GetMemberRequest) returns (GetMemberResponse);
  // Gets server member rows for multiple users. Requires admin.view-users.
  rpc BatchGetMembers(BatchGetMembersRequest) returns (BatchGetMembersResponse);
  // Assigns a role to a user. Requires role.assign.
  rpc AssignRole(AssignRoleRequest) returns (AssignRoleResponse);
  // Revokes a role from a user. Requires role.assign.
  rpc RevokeRole(RevokeRoleRequest) returns (RevokeRoleResponse);
  // Updates another user's login and/or display name as an admin action.
  // Requires user.manage-accounts; the caller cannot target their own account.
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  // Updates another user's password as an admin action. Requires
  // user.manage-accounts and a fresh credential for the caller; the caller
  // cannot target their own account.
  rpc UpdateUserPassword(UpdateUserPasswordRequest) returns (UpdateUserPasswordResponse);
  // Clears the target user's self-service username-change cooldown. Requires
  // user.manage-accounts.
  rpc ClearUsernameCooldown(ClearUsernameCooldownRequest) returns (ClearUsernameCooldownResponse);
  // Deletes a user account as an admin action. Requires user.delete-any for
  // other users, user.delete-self for the caller, and a fresh credential for
  // the caller.
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}
