syntax = "proto3";

package memos.api.v1;

import "api/v1/common.proto";
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";

option go_package = "gen/api/v1";

service UserService {
  // ListUsers returns a list of users.
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
    option (google.api.http) = {get: "/api/v1/users"};
  }

  // BatchGetUsers returns active users by usernames.
  rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse) {
    option (google.api.http) = {
      post: "/api/v1/users:batchGet"
      body: "*"
    };
  }

  // GetUser gets a user by username.
  // Format: users/{username} (e.g., users/steven)
  rpc GetUser(GetUserRequest) returns (User) {
    option (google.api.http) = {get: "/api/v1/{name=users/*}"};
    option (google.api.method_signature) = "name";
  }

  // CreateUser creates a new user.
  rpc CreateUser(CreateUserRequest) returns (User) {
    option (google.api.http) = {
      post: "/api/v1/users"
      body: "user"
    };
    option (google.api.method_signature) = "user";
  }

  // UpdateUser updates a user.
  rpc UpdateUser(UpdateUserRequest) returns (User) {
    option (google.api.http) = {
      patch: "/api/v1/{user.name=users/*}"
      body: "user"
    };
    option (google.api.method_signature) = "user,update_mask";
  }

  // DeleteUser deletes a user.
  rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {delete: "/api/v1/{name=users/*}"};
    option (google.api.method_signature) = "name";
  }

  // ListAllUserStats returns statistics for all users.
  rpc ListAllUserStats(ListAllUserStatsRequest) returns (ListAllUserStatsResponse) {
    option (google.api.http) = {get: "/api/v1/users:stats"};
  }

  // GetUserStats returns statistics for a specific user.
  rpc GetUserStats(GetUserStatsRequest) returns (UserStats) {
    option (google.api.http) = {get: "/api/v1/{name=users/*}:getStats"};
    option (google.api.method_signature) = "name";
  }

  // GetUserSetting returns the user setting.
  rpc GetUserSetting(GetUserSettingRequest) returns (UserSetting) {
    option (google.api.http) = {get: "/api/v1/{name=users/*/settings/*}"};
    option (google.api.method_signature) = "name";
  }

  // UpdateUserSetting updates the user setting.
  rpc UpdateUserSetting(UpdateUserSettingRequest) returns (UserSetting) {
    option (google.api.http) = {
      patch: "/api/v1/{setting.name=users/*/settings/*}"
      body: "setting"
    };
    option (google.api.method_signature) = "setting,update_mask";
  }

  // ListUserSettings returns a list of user settings.
  rpc ListUserSettings(ListUserSettingsRequest) returns (ListUserSettingsResponse) {
    option (google.api.http) = {get: "/api/v1/{parent=users/*}/settings"};
    option (google.api.method_signature) = "parent";
  }

  // ListPersonalAccessTokens returns a list of Personal Access Tokens (PATs) for a user.
  // PATs are long-lived tokens for API/script access, distinct from short-lived JWT access tokens.
  rpc ListPersonalAccessTokens(ListPersonalAccessTokensRequest) returns (ListPersonalAccessTokensResponse) {
    option (google.api.http) = {get: "/api/v1/{parent=users/*}/personalAccessTokens"};
    option (google.api.method_signature) = "parent";
  }

  // CreatePersonalAccessToken creates a new Personal Access Token for a user.
  // The token value is only returned once upon creation.
  rpc CreatePersonalAccessToken(CreatePersonalAccessTokenRequest) returns (CreatePersonalAccessTokenResponse) {
    option (google.api.http) = {
      post: "/api/v1/{parent=users/*}/personalAccessTokens"
      body: "*"
    };
  }

  // DeletePersonalAccessToken deletes a Personal Access Token.
  rpc DeletePersonalAccessToken(DeletePersonalAccessTokenRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {delete: "/api/v1/{name=users/*/personalAccessTokens/*}"};
    option (google.api.method_signature) = "name";
  }

  // ListUserWebhooks returns a list of webhooks for a user.
  rpc ListUserWebhooks(ListUserWebhooksRequest) returns (ListUserWebhooksResponse) {
    option (google.api.http) = {get: "/api/v1/{parent=users/*}/webhooks"};
    option (google.api.method_signature) = "parent";
  }

  // CreateUserWebhook creates a new webhook for a user.
  rpc CreateUserWebhook(CreateUserWebhookRequest) returns (UserWebhook) {
    option (google.api.http) = {
      post: "/api/v1/{parent=users/*}/webhooks"
      body: "webhook"
    };
    option (google.api.method_signature) = "parent,webhook";
  }

  // UpdateUserWebhook updates an existing webhook for a user.
  rpc UpdateUserWebhook(UpdateUserWebhookRequest) returns (UserWebhook) {
    option (google.api.http) = {
      patch: "/api/v1/{webhook.name=users/*/webhooks/*}"
      body: "webhook"
    };
    option (google.api.method_signature) = "webhook,update_mask";
  }

  // DeleteUserWebhook deletes a webhook for a user.
  rpc DeleteUserWebhook(DeleteUserWebhookRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {delete: "/api/v1/{name=users/*/webhooks/*}"};
    option (google.api.method_signature) = "name";
  }

  // ListUserNotifications lists notifications for a user.
  rpc ListUserNotifications(ListUserNotificationsRequest) returns (ListUserNotificationsResponse) {
    option (google.api.http) = {get: "/api/v1/{parent=users/*}/notifications"};
    option (google.api.method_signature) = "parent";
  }

  // UpdateUserNotification updates a notification.
  rpc UpdateUserNotification(UpdateUserNotificationRequest) returns (UserNotification) {
    option (google.api.http) = {
      patch: "/api/v1/{notification.name=users/*/notifications/*}"
      body: "notification"
    };
    option (google.api.method_signature) = "notification,update_mask";
  }

  // DeleteUserNotification deletes a notification.
  rpc DeleteUserNotification(DeleteUserNotificationRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {delete: "/api/v1/{name=users/*/notifications/*}"};
    option (google.api.method_signature) = "name";
  }
}

message User {
  option (google.api.resource) = {
    type: "memos.api.v1/User"
    pattern: "users/{user}"
    name_field: "name"
    singular: "user"
    plural: "users"
  };

  // The resource name of the user.
  // Format: users/{user}
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  // The role of the user.
  Role role = 2 [(google.api.field_behavior) = REQUIRED];

  // Required. The unique username for login.
  string username = 3 [(google.api.field_behavior) = REQUIRED];

  // Optional. The email address of the user.
  string email = 4 [(google.api.field_behavior) = OPTIONAL];

  // Optional. The display name of the user.
  string display_name = 5 [(google.api.field_behavior) = OPTIONAL];

  // Optional. The avatar URL of the user.
  string avatar_url = 6 [(google.api.field_behavior) = OPTIONAL];

  // Optional. The description of the user.
  string description = 7 [(google.api.field_behavior) = OPTIONAL];

  // Input only. The password for the user.
  string password = 8 [(google.api.field_behavior) = INPUT_ONLY];

  // The state of the user.
  State state = 9 [(google.api.field_behavior) = REQUIRED];

  // Output only. The creation timestamp.
  google.protobuf.Timestamp create_time = 10 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Output only. The last update timestamp.
  google.protobuf.Timestamp update_time = 11 [(google.api.field_behavior) = OUTPUT_ONLY];

  // User role enumeration.
  enum Role {
    ROLE_UNSPECIFIED = 0;
    // Admin role with system access.
    ADMIN = 2;
    // User role with limited access.
    USER = 3;
  }
}

message ListUsersRequest {
  // Optional. The maximum number of users to return.
  // The service may return fewer than this value.
  // If unspecified, at most 50 users will be returned.
  // The maximum value is 1000; values above 1000 will be coerced to 1000.
  int32 page_size = 1 [(google.api.field_behavior) = OPTIONAL];

  // Optional. A page token, received from a previous `ListUsers` call.
  // Provide this to retrieve the subsequent page.
  string page_token = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Filter to apply to the list results.
  // Example: "username == 'steven'"
  // Supported operators: ==
  // Supported fields: username
  string filter = 3 [(google.api.field_behavior) = OPTIONAL];

  // Optional. If true, show deleted users in the response.
  bool show_deleted = 4 [(google.api.field_behavior) = OPTIONAL];
}

message ListUsersResponse {
  // The list of users.
  repeated User users = 1;

  // A token that can be sent as `page_token` to retrieve the next page.
  // If this field is omitted, there are no subsequent pages.
  string next_page_token = 2;

  // The total count of users (may be approximate).
  int32 total_size = 3;
}

message BatchGetUsersRequest {
  repeated string usernames = 1;
}

message BatchGetUsersResponse {
  repeated User users = 1;
}

message GetUserRequest {
  // Required. The resource name of the user.
  // Format: users/{username}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // Optional. The fields to return in the response.
  // If not specified, all fields are returned.
  google.protobuf.FieldMask read_mask = 2 [(google.api.field_behavior) = OPTIONAL];
}

message CreateUserRequest {
  // Required. The user to create.
  User user = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.field_behavior) = INPUT_ONLY
  ];

  // Optional. The user ID to use for this user.
  // If empty, a unique ID will be generated.
  // Must match the pattern [a-z0-9-]+
  string user_id = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. If set, validate the request but don't actually create the user.
  bool validate_only = 3 [(google.api.field_behavior) = OPTIONAL];

  // Optional. An idempotency token that can be used to ensure that multiple
  // requests to create a user have the same result.
  string request_id = 4 [(google.api.field_behavior) = OPTIONAL];
}

message UpdateUserRequest {
  // Required. The user to update.
  User user = 1 [(google.api.field_behavior) = REQUIRED];

  // Required. The list of fields to update.
  google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];

  // Optional. If set to true, allows updating sensitive fields.
  bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL];
}

message DeleteUserRequest {
  // Required. The resource name of the user to delete.
  // Format: users/{user}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // Optional. If set to true, the user will be deleted even if they have associated data.
  bool force = 2 [(google.api.field_behavior) = OPTIONAL];
}

// User statistics messages
message UserStats {
  option (google.api.resource) = {
    type: "memos.api.v1/UserStats"
    pattern: "users/{user}"
    singular: "userStats"
    plural: "userStats"
  };

  // The resource name of the user whose stats these are.
  // Format: users/{user}
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  // The timestamps when the memos were displayed.
  repeated google.protobuf.Timestamp memo_display_timestamps = 2;

  // The stats of memo types.
  MemoTypeStats memo_type_stats = 3;

  // The count of tags.
  map<string, int32> tag_count = 4;

  // The pinned memos of the user.
  repeated string pinned_memos = 5;

  // Total memo count.
  int32 total_memo_count = 6;

  // Memo type statistics.
  message MemoTypeStats {
    int32 link_count = 1;
    int32 code_count = 2;
    int32 todo_count = 3;
    int32 undo_count = 4;
  }
}

message GetUserStatsRequest {
  // Required. The resource name of the user.
  // Format: users/{user}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];
}

message ListAllUserStatsRequest {
  // This endpoint doesn't take any parameters.
}

message ListAllUserStatsResponse {
  // The list of user statistics.
  repeated UserStats stats = 1;
}

// User settings message
message UserSetting {
  option (google.api.resource) = {
    type: "memos.api.v1/UserSetting"
    pattern: "users/{username}/settings/{setting}"
    singular: "userSetting"
    plural: "userSettings"
  };

  // The name of the user setting.
  // Format: users/{username}/settings/{setting}, {setting} is the key for the setting.
  // For example, "users/steven/settings/GENERAL" for general settings.
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  oneof value {
    GeneralSetting general_setting = 2;
    WebhooksSetting webhooks_setting = 5;
  }

  // Enumeration of user setting keys.
  enum Key {
    KEY_UNSPECIFIED = 0;
    // GENERAL is the key for general user settings.
    GENERAL = 1;
    // WEBHOOKS is the key for user webhooks.
    WEBHOOKS = 4;
  }

  // General user settings configuration.
  message GeneralSetting {
    // The preferred locale of the user.
    string locale = 1 [(google.api.field_behavior) = OPTIONAL];
    // The default visibility of the memo.
    string memo_visibility = 3 [(google.api.field_behavior) = OPTIONAL];
    // The preferred theme of the user.
    // This references a CSS file in the web/public/themes/ directory.
    // If not set, the default theme will be used.
    string theme = 4 [(google.api.field_behavior) = OPTIONAL];
  }

  // User webhooks configuration.
  message WebhooksSetting {
    // List of user webhooks.
    repeated UserWebhook webhooks = 1;
  }
}

message GetUserSettingRequest {
  // Required. The resource name of the user setting.
  // Format: users/{user}/settings/{setting}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/UserSetting"}
  ];
}

message UpdateUserSettingRequest {
  // Required. The user setting to update.
  UserSetting setting = 1 [(google.api.field_behavior) = REQUIRED];

  // Required. The list of fields to update.
  google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];
}

// Request message for ListUserSettings method.
message ListUserSettingsRequest {
  // Required. The parent resource whose settings will be listed.
  // Format: users/{user}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // Optional. The maximum number of settings to return.
  // The service may return fewer than this value.
  // If unspecified, at most 50 settings will be returned.
  // The maximum value is 1000; values above 1000 will be coerced to 1000.
  int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. A page token, received from a previous `ListUserSettings` call.
  // Provide this to retrieve the subsequent page.
  string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
}

// Response message for ListUserSettings method.
message ListUserSettingsResponse {
  // The list of user settings.
  repeated UserSetting settings = 1;

  // A token that can be sent as `page_token` to retrieve the next page.
  // If this field is omitted, there are no subsequent pages.
  string next_page_token = 2;

  // The total count of settings (may be approximate).
  int32 total_size = 3;
}

// PersonalAccessToken represents a long-lived token for API/script access.
// PATs are distinct from short-lived JWT access tokens used for session authentication.
message PersonalAccessToken {
  option (google.api.resource) = {
    type: "memos.api.v1/PersonalAccessToken"
    pattern: "users/{user}/personalAccessTokens/{personal_access_token}"
    singular: "personalAccessToken"
    plural: "personalAccessTokens"
  };

  // The resource name of the personal access token.
  // Format: users/{user}/personalAccessTokens/{personal_access_token}
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  // The description of the token.
  string description = 2 [(google.api.field_behavior) = OPTIONAL];

  // Output only. The creation timestamp.
  google.protobuf.Timestamp created_at = 3 [(google.api.field_behavior) = OUTPUT_ONLY];

  // Optional. The expiration timestamp.
  google.protobuf.Timestamp expires_at = 4 [(google.api.field_behavior) = OPTIONAL];

  // Output only. The last used timestamp.
  google.protobuf.Timestamp last_used_at = 5 [(google.api.field_behavior) = OUTPUT_ONLY];
}

message ListPersonalAccessTokensRequest {
  // Required. The parent resource whose personal access tokens will be listed.
  // Format: users/{user}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // Optional. The maximum number of tokens to return.
  int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. A page token for pagination.
  string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
}

message ListPersonalAccessTokensResponse {
  // The list of personal access tokens.
  repeated PersonalAccessToken personal_access_tokens = 1;

  // A token for the next page of results.
  string next_page_token = 2;

  // The total count of personal access tokens.
  int32 total_size = 3;
}

message CreatePersonalAccessTokenRequest {
  // Required. The parent resource where this token will be created.
  // Format: users/{user}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // Optional. Description of the personal access token.
  string description = 2 [(google.api.field_behavior) = OPTIONAL];

  // Optional. Expiration duration in days (0 = never expires).
  int32 expires_in_days = 3 [(google.api.field_behavior) = OPTIONAL];
}

message CreatePersonalAccessTokenResponse {
  // The personal access token metadata.
  PersonalAccessToken personal_access_token = 1;

  // The actual token value - only returned on creation.
  // This is the only time the token value will be visible.
  string token = 2;
}

message DeletePersonalAccessTokenRequest {
  // Required. The resource name of the personal access token to delete.
  // Format: users/{user}/personalAccessTokens/{personal_access_token}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/PersonalAccessToken"}
  ];
}

// UserWebhook represents a webhook owned by a user.
message UserWebhook {
  // The name of the webhook.
  // Format: users/{user}/webhooks/{webhook}
  string name = 1;

  // The URL to send the webhook to.
  string url = 2;

  // Optional. Human-readable name for the webhook.
  string display_name = 3;

  // The creation time of the webhook.
  google.protobuf.Timestamp create_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];

  // The last update time of the webhook.
  google.protobuf.Timestamp update_time = 5 [(google.api.field_behavior) = OUTPUT_ONLY];
}

message ListUserWebhooksRequest {
  // The parent user resource.
  // Format: users/{user}
  string parent = 1 [(google.api.field_behavior) = REQUIRED];
}

message ListUserWebhooksResponse {
  // The list of webhooks.
  repeated UserWebhook webhooks = 1;
}

message CreateUserWebhookRequest {
  // The parent user resource.
  // Format: users/{user}
  string parent = 1 [(google.api.field_behavior) = REQUIRED];

  // The webhook to create.
  UserWebhook webhook = 2 [(google.api.field_behavior) = REQUIRED];
}

message UpdateUserWebhookRequest {
  // The webhook to update.
  UserWebhook webhook = 1 [(google.api.field_behavior) = REQUIRED];

  // The list of fields to update.
  google.protobuf.FieldMask update_mask = 2;
}

message DeleteUserWebhookRequest {
  // The name of the webhook to delete.
  // Format: users/{user}/webhooks/{webhook}
  string name = 1 [(google.api.field_behavior) = REQUIRED];
}

message UserNotification {
  option (google.api.resource) = {
    type: "memos.api.v1/UserNotification"
    pattern: "users/{user}/notifications/{notification}"
    name_field: "name"
    singular: "notification"
    plural: "notifications"
  };

  // The resource name of the notification.
  // Format: users/{user}/notifications/{notification}
  string name = 1 [
    (google.api.field_behavior) = OUTPUT_ONLY,
    (google.api.field_behavior) = IDENTIFIER
  ];

  // The sender of the notification.
  // Format: users/{user}
  string sender = 2 [
    (google.api.field_behavior) = OUTPUT_ONLY,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  // The sender user details.
  User sender_user = 8 [(google.api.field_behavior) = OUTPUT_ONLY];

  // The status of the notification.
  Status status = 3 [(google.api.field_behavior) = OPTIONAL];

  // The creation timestamp.
  google.protobuf.Timestamp create_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];

  // The type of the notification.
  Type type = 5 [(google.api.field_behavior) = OUTPUT_ONLY];

  oneof payload {
    MemoCommentPayload memo_comment = 6 [(google.api.field_behavior) = OUTPUT_ONLY];
    MemoMentionPayload memo_mention = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
  }

  message MemoCommentPayload {
    // The memo name of comment.
    // Format: memos/{memo}
    string memo = 1;

    // The name of related memo.
    // Format: memos/{memo}
    string related_memo = 2;

    // Preview text of the comment memo.
    string memo_snippet = 3;

    // Preview text of the related memo.
    string related_memo_snippet = 4;
  }

  message MemoMentionPayload {
    // The memo that contains the mention.
    // Format: memos/{memo}
    string memo = 1;

    // The related parent memo when the mention was created in a comment.
    // Format: memos/{memo}
    string related_memo = 2;

    // Preview text of the memo that contains the mention.
    string memo_snippet = 3;

    // Preview text of the related parent memo.
    string related_memo_snippet = 4;
  }

  enum Status {
    STATUS_UNSPECIFIED = 0;
    UNREAD = 1;
    ARCHIVED = 2;
  }

  enum Type {
    TYPE_UNSPECIFIED = 0;
    MEMO_COMMENT = 1;
    MEMO_MENTION = 2;
  }
}

message ListUserNotificationsRequest {
  // The parent user resource.
  // Format: users/{user}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/User"}
  ];

  int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];
  string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
  string filter = 4 [(google.api.field_behavior) = OPTIONAL];
}

message ListUserNotificationsResponse {
  repeated UserNotification notifications = 1;
  string next_page_token = 2;
}

message UpdateUserNotificationRequest {
  UserNotification notification = 1 [(google.api.field_behavior) = REQUIRED];
  google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];
}

message DeleteUserNotificationRequest {
  // Format: users/{user}/notifications/{notification}
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {type: "memos.api.v1/UserNotification"}
  ];
}
