// Copyright (c) 2026 Nikolay Govorov
// SPDX-License-Identifier: AGPL-3.0-or-later

syntax = "proto3";

package mirum.api;

import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";

option go_package = "dimidiumlabs/mirum/cmd/mirum-server/apipb";

service Console {
  rpc UserCreate(UserCreateRequest) returns (UserCreateResponse);
  rpc UserGet(UserGetRequest) returns (UserGetResponse);
  rpc UserList(UserListRequest) returns (UserListResponse);
  rpc UserUpdate(UserUpdateRequest) returns (UserUpdateResponse);
  rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse);

  rpc OrgCreate(OrgCreateRequest) returns (OrgCreateResponse);
  rpc OrgGet(OrgGetRequest) returns (OrgGetResponse);
  rpc OrgList(OrgListRequest) returns (OrgListResponse);
  rpc OrgUpdate(OrgUpdateRequest) returns (OrgUpdateResponse);
  rpc OrgDelete(OrgDeleteRequest) returns (OrgDeleteResponse);

  rpc OrgMemberAdd(OrgMemberAddRequest) returns (OrgMemberAddResponse);
  rpc OrgMemberGet(OrgMemberGetRequest) returns (OrgMemberGetResponse);
  rpc OrgMemberList(OrgMemberListRequest) returns (OrgMemberListResponse);
  rpc OrgMemberUpdate(OrgMemberUpdateRequest) returns (OrgMemberUpdateResponse);
  rpc OrgMemberRemove(OrgMemberRemoveRequest) returns (OrgMemberRemoveResponse);

  rpc WorkerCreate(WorkerCreateRequest) returns (WorkerCreateResponse);
  rpc WorkerGet(WorkerGetRequest) returns (WorkerGetResponse);
  rpc WorkerList(WorkerListRequest) returns (WorkerListResponse);
  rpc WorkerDelete(WorkerDeleteRequest) returns (WorkerDeleteResponse);
}

// Common types

enum Role {
  ROLE_NONE = 0;
  ROLE_OWNER = 1;
  ROLE_ADMIN = 2;
  ROLE_MEMBER = 3;
}

enum Perm {
  PERM_NONE = 0;

  PERM_USER_READ = 1;
  PERM_USER_WRITE = 2;
  PERM_USER_DELETE = 3;

  PERM_ORG_READ = 4;
  PERM_ORG_WRITE = 5;
  PERM_ORG_DELETE = 6;
  PERM_ORG_MEMBER_READ = 7;
  PERM_ORG_MEMBER_WRITE = 8;

  PERM_WORKER_READ = 9;
  PERM_WORKER_WRITE = 10;
}

// ErrorInfo is attached as a ConnectError detail on every error.
// The ConnectError message field is always empty — the server never sends
// human-readable text. Clients switch on reason to select user-facing copy.
message ErrorInfo {
  ErrorReason reason = 1;

  // Short stable identifiers for UI targeting (e.g. {"field": "slug"}).
  // Never human-readable text.
  map<string, string> metadata = 2;
}

enum ErrorReason {
  ERROR_REASON_UNSPECIFIED = 0;
  ERROR_REASON_INTERNAL = 1;

  // Lookup failures
  ERROR_REASON_USER_NOT_FOUND = 10;
  ERROR_REASON_ORG_NOT_FOUND = 11;
  ERROR_REASON_WORKER_NOT_FOUND = 12;
  ERROR_REASON_MEMBER_NOT_FOUND = 13;

  // Conflicts
  ERROR_REASON_EMAIL_TAKEN = 20;
  ERROR_REASON_SLUG_TAKEN = 21;
  ERROR_REASON_ALREADY_MEMBER = 22;

  // State preconditions
  ERROR_REASON_LAST_OWNER = 30;
  ERROR_REASON_SOLE_OWNER = 31;

  // Validation
  ERROR_REASON_INVALID_SLUG = 40;
  ERROR_REASON_INVALID_ROLE = 41;
  ERROR_REASON_RESERVED_EMAIL = 42;

  // Auth
  ERROR_REASON_UNAUTHENTICATED = 50;
  ERROR_REASON_PERMISSION_DENIED = 51;
  ERROR_REASON_INVALID_CREDENTIALS = 52;
  ERROR_REASON_INVALID_CSRF = 53;

  // Infrastructure
  ERROR_REASON_RATE_LIMITED = 60;
  ERROR_REASON_UNAVAILABLE = 61;
  ERROR_REASON_UNIMPLEMENTED = 62;
}

message UserRef {
  oneof ref {
    option (buf.validate.oneof).required = true;
    bytes id = 1 [(buf.validate.field).bytes = {
      min_len: 16
      max_len: 16
    }];
    string email = 2 [(buf.validate.field).string.email = true];
  }
}

message OrgRef {
  oneof ref {
    option (buf.validate.oneof).required = true;
    bytes id = 1 [(buf.validate.field).bytes = {
      min_len: 16
      max_len: 16
    }];
    string slug = 2 [(buf.validate.field).string = {
      min_len: 2
      max_len: 64
    }];
  }
}

message PageRequest {
  bytes cursor = 1;
  int32 page_size = 2;
}
message PageResponse {
  bytes next_cursor = 1;
  int32 total_count = 2;
}

// User management

message User {
  bytes id = 1;
  string email = 2;
  google.protobuf.Timestamp created_at = 3;
}

message UserCreateRequest {
  string email = 1 [(buf.validate.field).string.email = true];
  string password = 2 [(buf.validate.field).string.min_len = 1];
}
message UserCreateResponse {
  bytes id = 1;
}

message UserGetRequest {
  UserRef user = 1 [(buf.validate.field).required = true];
}
message UserGetResponse {
  User user = 1;
}

message UserListRequest {
  optional PageRequest page = 1;
  optional string filter = 2;
}
message UserListResponse {
  PageResponse page = 1;
  repeated User users = 2;
}

message UserUpdateRequest {
  UserRef user = 1 [(buf.validate.field).required = true];
  optional string email = 2 [(buf.validate.field).string.email = true];
  optional string password = 3 [(buf.validate.field).string.min_len = 1];
}
message UserUpdateResponse {}

message UserDeleteRequest {
  UserRef user = 1 [(buf.validate.field).required = true];
}
message UserDeleteResponse {}

// Organization management

message Org {
  bytes id = 1;
  string name = 2;
  string slug = 3;
  bool public = 4;
  google.protobuf.Timestamp created_at = 5;
}

message OrgCreateRequest {
  string name = 1 [(buf.validate.field).string.min_len = 1];
  string slug = 2 [(buf.validate.field).string = {
    min_len: 2
    max_len: 64
  }];
  UserRef owner = 3 [(buf.validate.field).required = true];
  bool public = 4;
}
message OrgCreateResponse {
  bytes id = 1;
}

message OrgGetRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
}
message OrgGetResponse {
  Org org = 1;
}

message OrgListRequest {
  optional PageRequest page = 1;
  optional string filter = 2;
}
message OrgListResponse {
  PageResponse page = 1;
  repeated Org organizations = 2;
}

message OrgUpdateRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
  optional string name = 2 [(buf.validate.field).string.min_len = 1];
  optional string slug = 3 [(buf.validate.field).string = {
    min_len: 2
    max_len: 64
  }];
  optional bool public = 4;
}
message OrgUpdateResponse {}

message OrgDeleteRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
}
message OrgDeleteResponse {}

// Organization members

message OrgMemberInfo {
  User user = 1;
  Role role = 2;
  google.protobuf.Timestamp joined_at = 3;
}

message OrgMemberGetRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
  UserRef user = 2 [(buf.validate.field).required = true];
}
message OrgMemberGetResponse {
  OrgMemberInfo member = 1;
}

message OrgMemberAddRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
  UserRef user = 2 [(buf.validate.field).required = true];
  Role role = 3 [(buf.validate.field).enum = {
    defined_only: true
    not_in: [0]
  }];
}
message OrgMemberAddResponse {}

message OrgMemberListRequest {
  optional PageRequest page = 1;
  OrgRef org = 2 [(buf.validate.field).required = true];
  optional string filter = 3;
}
message OrgMemberListResponse {
  PageResponse page = 1;
  repeated OrgMemberInfo members = 2;
}

message OrgMemberUpdateRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
  UserRef user = 2 [(buf.validate.field).required = true];
  Role role = 3 [(buf.validate.field).enum = {
    defined_only: true
    not_in: [0]
  }];
}
message OrgMemberUpdateResponse {}

message OrgMemberRemoveRequest {
  OrgRef org = 1 [(buf.validate.field).required = true];
  UserRef user = 2 [(buf.validate.field).required = true];
}
message OrgMemberRemoveResponse {}

// Worker management

message Worker {
  bytes id = 1;
  bytes public_key = 2;
  optional bytes org_id = 3;
  google.protobuf.Timestamp created_at = 4;
}

message WorkerCreateRequest {
  bytes public_key = 1 [(buf.validate.field).bytes = {
    min_len: 32
    max_len: 32
  }];
  optional OrgRef org = 2;
}
message WorkerCreateResponse {
  bytes id = 1;
}

message WorkerGetRequest {
  bytes id = 1 [(buf.validate.field).bytes = {
    min_len: 16
    max_len: 16
  }];
}
message WorkerGetResponse {
  Worker worker = 1;
}

message WorkerListRequest {
  optional PageRequest page = 1;
  optional string filter = 2;
}
message WorkerListResponse {
  PageResponse page = 1;
  repeated Worker workers = 2;
}

message WorkerDeleteRequest {
  bytes id = 1 [(buf.validate.field).bytes = {
    min_len: 16
    max_len: 16
  }];
}
message WorkerDeleteResponse {}
