/**
 * Generated by orval v8.5.3 🍺
 * Do not edit manually.
 * Api
 * Tx OS API specification
 * OpenAPI spec version: 0.1.0
 */
export interface HealthStatus {
  status: string;
}

export interface SystemVersionInfo {
  version: string;
  build: string;
}

export type SystemVersionResponseStatus =
  (typeof SystemVersionResponseStatus)[keyof typeof SystemVersionResponseStatus];

export const SystemVersionResponseStatus = {
  "up-to-date": "up-to-date",
  "update-available": "update-available",
  "not-configured": "not-configured",
  error: "error",
} as const;

export interface SystemVersionResponse {
  current: SystemVersionInfo;
  latest: SystemVersionInfo | null;
  status: SystemVersionResponseStatus;
  /** @nullable */
  errorMessage: string | null;
  /** @nullable */
  checkedAt: string | null;
  configured: boolean;
  /** ISO-8601 timestamp captured when the API process booted.
Frozen for the lifetime of the process — used by the admin
System Updates panel as visible proof that the container
actually restarted (independent of build/version changes).
 */
  startedAt: string;
}

export interface ErrorResponse {
  error: string;
}

export interface SuccessResponse {
  success: boolean;
}

export interface RegisterBody {
  username: string;
  email: string;
  password: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  preferredLanguage?: string;
}

export interface CreateUserBody {
  username: string;
  email: string;
  password: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  preferredLanguage?: string;
  /** Initial group memberships in addition to the auto-assigned Everyone group. */
  groupIds?: number[];
}

export interface LoginBody {
  username: string;
  password: string;
}

export interface ForgotPasswordBody {
  /** Username or email */
  identifier: string;
}

export interface ForgotPasswordResponse {
  success: boolean;
}

export interface ResetPasswordBody {
  token: string;
  /** @minLength 6 */
  newPassword: string;
}

export interface VerifyResetTokenBody {
  token: string;
}

export interface AdminResetLinkResponse {
  resetUrl: string;
  expiresAt: string;
}

export interface VerifyResetTokenResponse {
  valid: boolean;
}

export interface UpdateLanguageBody {
  language: string;
}

/**
 * Per-user home-screen clock style preference
 */
export type ClockStyle = (typeof ClockStyle)[keyof typeof ClockStyle];

export const ClockStyle = {
  full: "full",
  digital: "digital",
  "digital-no-seconds": "digital-no-seconds",
  analog: "analog",
  minimal: "minimal",
} as const;

export interface UpdateClockStyleBody {
  /** Clock style; pass null to clear and use the default */
  clockStyle: ClockStyle | null;
}

export type NotificationSoundId =
  (typeof NotificationSoundId)[keyof typeof NotificationSoundId];

export const NotificationSoundId = {
  ding: "ding",
  chime: "chime",
  bell: "bell",
  knock: "knock",
  pop: "pop",
  alert: "alert",
  beep: "beep",
  soft: "soft",
} as const;

export interface UpdateNotificationPreferencesBody {
  notificationSoundOrder?: NotificationSoundId;
  notificationSoundMeeting?: NotificationSoundId;
  notificationSoundNote?: NotificationSoundId;
  notifyOrdersEnabled?: boolean;
  notifyMeetingsEnabled?: boolean;
  notifyNotesEnabled?: boolean;
  vibrationEnabledOrder?: boolean;
  vibrationEnabledMeeting?: boolean;
  vibrationEnabledNote?: boolean;
  notificationsMuted?: boolean;
}

export interface UpdateClockHour12Body {
  /**
   * Whether to use 12-hour (AM/PM) clock format; null clears and uses the default (24-hour)
   * @nullable
   */
  clockHour12: boolean | null;
}

export interface GroupSummary {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
}

export interface AuthUser {
  id: number;
  username: string;
  email: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  preferredLanguage: string;
  clockStyle?: ClockStyle | null;
  /** @nullable */
  clockHour12?: boolean | null;
  /** @nullable */
  avatarUrl?: string | null;
  isActive: boolean;
  notificationSoundOrder: NotificationSoundId;
  notificationSoundMeeting: NotificationSoundId;
  notificationSoundNote: NotificationSoundId;
  notifyOrdersEnabled: boolean;
  notifyMeetingsEnabled: boolean;
  notifyNotesEnabled: boolean;
  vibrationEnabledOrder: boolean;
  vibrationEnabledMeeting: boolean;
  vibrationEnabledNote: boolean;
  notificationsMuted: boolean;
  roles: string[];
  groups: GroupSummary[];
  createdAt: string;
}

export interface UserProfile {
  id: number;
  username: string;
  email: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  preferredLanguage: string;
  clockStyle?: ClockStyle | null;
  /** @nullable */
  clockHour12?: boolean | null;
  /** @nullable */
  avatarUrl?: string | null;
  isActive: boolean;
  roles: string[];
  /** Roles assigned directly to the user (user_roles table only). The
`roles` field above is the effective set (direct + inherited via
group membership). The admin Users page uses the difference to
mark a role as "inherited from group X" and disables the toggle
so the admin doesn't try to remove a role that won't disappear.
 */
  directRoles?: string[];
  groups: GroupSummary[];
  createdAt: string;
  /** Dependent record counts. Populated only by the admin list endpoint
(GET /users) so the delete dialog can warn before the first click.
Omitted from single-user responses (GET /users/:id, PATCH, etc).
 */
  noteCount?: number;
  /** Number of service orders owned by this user. Populated only by the
admin list endpoint (GET /users).
 */
  orderCount?: number;
}

export interface UpdateUserBody {
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  isActive?: boolean;
  preferredLanguage?: string;
  /** If provided, replaces the user's group memberships */
  groupIds?: number[];
}

export interface AddUserRoleBody {
  /** @minLength 1 */
  roleName: string;
}

export interface Role {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  isSystem: boolean;
  createdAt: string;
  /** Number of users assigned this role. Populated only by the admin
list endpoint (GET /admin/roles) so the admin list can show the
same dependency counts inline as the Apps/Services/Users/Groups
panels. Omitted from create/update responses.
 */
  userCount?: number;
  /** Number of groups granting this role. Populated only by the admin
list endpoint (GET /admin/roles).
 */
  groupCount?: number;
}

export interface CreateRoleBody {
  /** @minLength 1 */
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
}

export interface UpdateRoleBody {
  /** @minLength 1 */
  name?: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
}

export interface RoleUsage {
  userCount: number;
  groupCount: number;
}

export interface RoleDeletionConflict {
  error: string;
  userCount: number;
  groupCount: number;
}

export interface Permission {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
}

export interface AddAppPermissionBody {
  /** ID of the permission to require for this app. */
  permissionId: number;
}

export interface AppPermissionLink {
  appId: number;
  permissionId: number;
}

export interface AppPermissionsImpactBody {
  /** Candidate set of permission IDs that would gate this app. Pass an empty array to model "remove every requirement" (i.e. make the app unrestricted). */
  permissionIds: number[];
}

export interface AppPermissionImpactGroup {
  id: number;
  name: string;
}

export interface AppPermissionsImpact {
  /** Distinct non-admin users who currently see the app and would lose visibility under the candidate permission set. Already accounts for users who keep access via `group_apps` group-grants. */
  affectedUserCount: number;
  /** Distinct non-admin users who currently see the app under the existing permission set. Useful for showing the warning as a fraction (e.g. "12 of 80 will lose access"). */
  currentlyVisibleUserCount: number;
  /** Groups that currently grant this app via `group_apps`. Members of any of these groups keep access regardless of permission changes. */
  groups: AppPermissionImpactGroup[];
  /** True when the candidate set is identical to the current permission set so no change would actually be saved. */
  noChange: boolean;
}

export interface ReplaceRolePermissionsBody {
  /** Full set of permission IDs the role should grant. Existing assignments not present here are removed. */
  permissionIds: number[];
}

export interface RolePermissionsImpactBody {
  /** Candidate set of permission IDs to evaluate against the role's current assignments. */
  permissionIds: number[];
}

export interface RolePermissionImpactGroup {
  id: number;
  name: string;
}

export interface RolePermissionImpactItem {
  permissionId: number;
  permissionName: string;
  /** Distinct users who currently have this permission via this role and would lose it (no other role they hold grants the permission). */
  userCount: number;
  /** Number of groups that currently grant this role. */
  groupCount: number;
  /** Groups that currently grant this role and therefore propagate the removal to their members. */
  groups: RolePermissionImpactGroup[];
}

export interface RolePermissionsImpact {
  /** One entry per permission that is currently assigned to the role but not in the candidate set. */
  removed: RolePermissionImpactItem[];
  /** Distinct users that would lose at least one permission across all removals. */
  totalAffectedUsers: number;
}

export interface Group {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  isSystem: boolean;
  memberCount: number;
  appCount: number;
  roleCount: number;
  createdAt: string;
}

export interface GroupDeletionConflict {
  error: string;
  memberCount: number;
  appCount: number;
  roleCount: number;
}

export interface UserDeletionConflict {
  error: string;
  noteCount: number;
  orderCount: number;
}

export interface AppDeletionConflict {
  error: string;
  groupCount: number;
  restrictionCount: number;
  openCount: number;
}

export interface ServiceDeletionConflict {
  error: string;
  orderCount: number;
}

export interface GroupDetail {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  isSystem: boolean;
  appIds: number[];
  roleIds: number[];
  userIds: number[];
  createdAt: string;
}

export interface CreateGroupBody {
  /** @minLength 1 */
  name: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  appIds?: number[];
  roleIds?: number[];
  userIds?: number[];
}

export interface UpdateGroupBody {
  /** @minLength 1 */
  name?: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  appIds?: number[];
  roleIds?: number[];
  userIds?: number[];
}

/**
 * How the launcher opens this app: "internal" navigates to `route`
inside the SPA; "external_tab" opens externalUrl in a new tab;
"external_iframe" navigates to /embedded/:id, which renders
externalUrl inside an iframe shell.

 */
export type AppOpenMode = (typeof AppOpenMode)[keyof typeof AppOpenMode];

export const AppOpenMode = {
  internal: "internal",
  external_tab: "external_tab",
  external_iframe: "external_iframe",
} as const;

export interface App {
  id: number;
  slug: string;
  nameAr: string;
  nameEn: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  iconName: string;
  /**
   * Optional uploaded image used in place of the Lucide icon on the
launcher tile. Stored as the object-storage path returned by the
upload presigner (e.g. "/objects/<id>").

   * @nullable
   */
  imageUrl?: string | null;
  route: string;
  /**
   * For openMode "external_tab" or "external_iframe": the URL the
launcher should open. Ignored when openMode is "internal".

   * @nullable
   */
  externalUrl?: string | null;
  /** How the launcher opens this app: "internal" navigates to `route`
inside the SPA; "external_tab" opens externalUrl in a new tab;
"external_iframe" navigates to /embedded/:id, which renders
externalUrl inside an iframe shell.
 */
  openMode: AppOpenMode;
  color: string;
  isActive: boolean;
  isSystem: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
  /** Number of groups granting access to this app. Populated only by the
admin list endpoint (GET /admin/apps) so the delete dialog can warn
before the first click. Omitted from non-admin and single-app
responses.
 */
  groupCount?: number;
  /** Number of legacy permission restrictions for this app. Populated
only by the admin list endpoint (GET /admin/apps).
 */
  restrictionCount?: number;
  /** Number of recorded app-open events for this app. Populated only by
the admin list endpoint (GET /admin/apps).
 */
  openCount?: number;
}

export type CreateAppBodyOpenMode =
  (typeof CreateAppBodyOpenMode)[keyof typeof CreateAppBodyOpenMode];

export const CreateAppBodyOpenMode = {
  internal: "internal",
  external_tab: "external_tab",
  external_iframe: "external_iframe",
} as const;

export interface CreateAppBody {
  slug: string;
  nameAr: string;
  nameEn: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  iconName: string;
  /** @nullable */
  imageUrl?: string | null;
  route: string;
  /** @nullable */
  externalUrl?: string | null;
  openMode?: CreateAppBodyOpenMode;
  color: string;
  isActive?: boolean;
  isSystem?: boolean;
  sortOrder?: number;
  /** Optional set of permission IDs to gate the new app on. Inserted with onConflictDoNothing so duplicates are tolerated. */
  permissionIds?: number[];
}

export type UpdateAppBodyOpenMode =
  (typeof UpdateAppBodyOpenMode)[keyof typeof UpdateAppBodyOpenMode];

export const UpdateAppBodyOpenMode = {
  internal: "internal",
  external_tab: "external_tab",
  external_iframe: "external_iframe",
} as const;

export interface UpdateAppBody {
  nameAr?: string;
  nameEn?: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  iconName?: string;
  /** @nullable */
  imageUrl?: string | null;
  route?: string;
  /** @nullable */
  externalUrl?: string | null;
  openMode?: UpdateAppBodyOpenMode;
  color?: string;
  isActive?: boolean;
  sortOrder?: number;
}

export interface Service {
  id: number;
  /** @nullable */
  categoryId?: number | null;
  nameAr: string;
  nameEn: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  /** @nullable */
  imageUrl?: string | null;
  /** @nullable */
  price?: string | null;
  isAvailable: boolean;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
  /** Number of service_orders rows referencing this service. Populated
only by the list endpoint (GET /services) so the admin delete
dialog can warn before the first click. Omitted from single-service
responses.
 */
  orderCount?: number;
}

export interface CreateServiceBody {
  /** @nullable */
  categoryId?: number | null;
  nameAr: string;
  nameEn: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  /** @nullable */
  imageUrl?: string | null;
  /** @nullable */
  price?: string | null;
  isAvailable?: boolean;
  sortOrder?: number;
}

export interface UpdateServiceBody {
  /** @nullable */
  categoryId?: number | null;
  nameAr?: string;
  nameEn?: string;
  /** @nullable */
  descriptionAr?: string | null;
  /** @nullable */
  descriptionEn?: string | null;
  /** @nullable */
  imageUrl?: string | null;
  /** @nullable */
  price?: string | null;
  isAvailable?: boolean;
  sortOrder?: number;
}

/**
 * New global service order. `orderedIds` must contain every
existing service id exactly once. The server sets each
service's `sortOrder` to its position in the list (0-based),
so a later GET /services returns them in the same order.

 */
export interface ReorderServicesBody {
  /**
   * @minItems 1
   * @maxItems 1000
   */
  orderedIds: number[];
}

export interface ServiceCategory {
  id: number;
  nameAr: string;
  nameEn: string;
  sortOrder: number;
  createdAt: string;
}

export interface ServiceOrderUser {
  id: number;
  username: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  /** @nullable */
  avatarUrl?: string | null;
}

export interface ServiceOrderService {
  id: number;
  nameAr: string;
  nameEn: string;
  /** @nullable */
  imageUrl?: string | null;
}

export type ServiceOrderStatus =
  (typeof ServiceOrderStatus)[keyof typeof ServiceOrderStatus];

export const ServiceOrderStatus = {
  pending: "pending",
  received: "received",
  preparing: "preparing",
  completed: "completed",
  cancelled: "cancelled",
} as const;

export interface ServiceOrder {
  id: number;
  serviceId: number;
  userId: number;
  /** @nullable */
  assignedTo?: number | null;
  /** @nullable */
  notes?: string | null;
  status: ServiceOrderStatus;
  createdAt: string;
  updatedAt: string;
  service: ServiceOrderService;
  requester?: ServiceOrderUser | null;
  assignee?: ServiceOrderUser | null;
}

export interface CreateServiceOrderBody {
  serviceId: number;
  /**
   * @maxLength 500
   * @nullable
   */
  notes?: string | null;
}

export type UpdateServiceOrderStatusBodyStatus =
  (typeof UpdateServiceOrderStatusBodyStatus)[keyof typeof UpdateServiceOrderStatusBodyStatus];

export const UpdateServiceOrderStatusBodyStatus = {
  pending: "pending",
  received: "received",
  preparing: "preparing",
  completed: "completed",
  cancelled: "cancelled",
} as const;

export interface UpdateServiceOrderStatusBody {
  status: UpdateServiceOrderStatusBodyStatus;
}

export interface BulkDeleteServiceOrdersBody {
  /**
   * @minItems 1
   * @maxItems 200
   */
  ids: number[];
}

export interface BulkDeleteServiceOrdersResponse {
  /** Ids that were successfully deleted. */
  deletedIds: number[];
  /** Ids that were skipped (not found, or caller is not authorized). */
  failedIds: number[];
}

export interface Notification {
  id: number;
  userId: number;
  titleAr: string;
  titleEn: string;
  /** @nullable */
  bodyAr?: string | null;
  /** @nullable */
  bodyEn?: string | null;
  type: string;
  isRead: boolean;
  /** @nullable */
  relatedId?: number | null;
  /** @nullable */
  relatedType?: string | null;
  createdAt: string;
}

export interface AppSettings {
  siteNameAr: string;
  siteNameEn: string;
  registrationOpen: boolean;
  footerTextAr: string;
  footerTextEn: string;
  updatedAt?: string;
}

export interface UpdateMyAppOrderBody {
  /** App IDs in the desired display order */
  order: number[];
}

export interface UpdateAppSettingsBody {
  /**
   * @minLength 1
   * @maxLength 200
   */
  siteNameAr?: string;
  /**
   * @minLength 1
   * @maxLength 200
   */
  siteNameEn?: string;
  registrationOpen?: boolean;
  /**
   * @minLength 1
   * @maxLength 300
   */
  footerTextAr?: string;
  /**
   * @minLength 1
   * @maxLength 300
   */
  footerTextEn?: string;
}

export interface RequestUploadUrlBody {
  /** @minLength 1 */
  name: string;
  /** @minimum 1 */
  size: number;
  /** @minLength 1 */
  contentType: string;
}

export interface RequestUploadUrlResponse {
  /**
   * Presigned upload destination. May be either an absolute URL (S3 driver) or a same-origin path starting with `/` (local driver) — the browser resolves the latter against `window.location.origin` so uploads always target the host serving the SPA.

   * @minLength 1
   */
  uploadURL: string;
  objectPath: string;
  metadata?: RequestUploadUrlBody;
}

export interface HomeStats {
  totalApps: number;
  totalServices: number;
  unreadNotifications: number;
  totalUsers: number;
}

export type AdminStatsRange =
  (typeof AdminStatsRange)[keyof typeof AdminStatsRange];

export const AdminStatsRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export type AdminStatsSignupsByDayItem = {
  date: string;
  count: number;
};

export type AdminStatsAppOpensByDayItem = {
  date: string;
  count: number;
};

export type AdminStatsServicesCreatedByDayItem = {
  date: string;
  count: number;
};

export interface TopAppItem {
  appId: number;
  slug: string;
  nameAr: string;
  nameEn: string;
  iconName: string;
  color: string;
  count: number;
}

export interface TopUserItem {
  userId: number;
  username: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  /** @nullable */
  avatarUrl?: string | null;
  count: number;
}

export interface AdminStats {
  range: AdminStatsRange;
  rangeDays: number;
  /** Start date (YYYY-MM-DD UTC) of the selected window */
  rangeFrom?: string;
  /** End date (YYYY-MM-DD UTC) of the selected window */
  rangeTo?: string;
  newUsersInRange: number;
  newUsersPrevRange: number;
  activeServices: number;
  inactiveServices: number;
  signupsByDay: AdminStatsSignupsByDayItem[];
  appOpensByDay: AdminStatsAppOpensByDayItem[];
  appOpensInRange: number;
  appOpensPrevRange: number;
  servicesCreatedByDay: AdminStatsServicesCreatedByDayItem[];
  servicesCreatedInRange: number;
  topApps: TopAppItem[];
  mostActiveUsers: TopUserItem[];
}

export interface AppOpenByAppEntry {
  id: number;
  createdAt: string;
  userId: number;
  username: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  /** @nullable */
  avatarUrl?: string | null;
}

export type AdminAppOpensByAppRange =
  (typeof AdminAppOpensByAppRange)[keyof typeof AdminAppOpensByAppRange];

export const AdminAppOpensByAppRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export interface AdminAppOpensByApp {
  appId: number;
  slug: string;
  nameAr: string;
  nameEn: string;
  iconName: string;
  color: string;
  range: AdminAppOpensByAppRange;
  rangeDays: number;
  rangeFrom: string;
  rangeTo: string;
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
  opens: AppOpenByAppEntry[];
}

export interface AppOpenByUserEntry {
  id: number;
  createdAt: string;
  appId: number;
  slug: string;
  nameAr: string;
  nameEn: string;
  iconName: string;
  color: string;
}

export type AdminAppOpensByUserRange =
  (typeof AdminAppOpensByUserRange)[keyof typeof AdminAppOpensByUserRange];

export const AdminAppOpensByUserRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export interface AdminAppOpensByUser {
  userId: number;
  username: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  /** @nullable */
  avatarUrl?: string | null;
  range: AdminAppOpensByUserRange;
  rangeDays: number;
  rangeFrom: string;
  rangeTo: string;
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
  opens: AppOpenByUserEntry[];
}

export interface AppDependentGroupItem {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr: string | null;
  /** @nullable */
  descriptionEn: string | null;
}

export interface AppDependentGroupsPage {
  items: AppDependentGroupItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface AppDependentRestrictionItem {
  id: number;
  name: string;
  /** @nullable */
  descriptionAr: string | null;
  /** @nullable */
  descriptionEn: string | null;
  /** Names of roles currently holding this permission. */
  roles: string[];
}

export interface AppDependentRestrictionsPage {
  items: AppDependentRestrictionItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface AppDependentOpenItem {
  id: number;
  createdAt: string;
  userId: number;
  username: string;
  /** @nullable */
  displayNameAr: string | null;
  /** @nullable */
  displayNameEn: string | null;
  /** @nullable */
  avatarUrl: string | null;
}

export interface AppDependentOpensPage {
  items: AppDependentOpenItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface ServiceDependentOrderItem {
  id: number;
  status: string;
  /** @nullable */
  notes: string | null;
  createdAt: string;
  userId: number;
  username: string;
  /** @nullable */
  displayNameAr: string | null;
  /** @nullable */
  displayNameEn: string | null;
}

export interface ServiceDependentOrdersPage {
  items: ServiceDependentOrderItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface UserDependentNoteItem {
  id: number;
  title: string;
  isPinned: boolean;
  isArchived: boolean;
  updatedAt: string;
}

export type NoteRecipientStatus =
  (typeof NoteRecipientStatus)[keyof typeof NoteRecipientStatus];

export const NoteRecipientStatus = {
  unread: "unread",
  read: "read",
  replied: "replied",
  archived: "archived",
} as const;

export interface NoteUserSummary {
  id: number;
  username: string;
  /** @nullable */
  displayNameAr: string | null;
  /** @nullable */
  displayNameEn: string | null;
  /** @nullable */
  avatarUrl?: string | null;
  isActive?: boolean;
}

export interface NoteRecipientSummary {
  id: number;
  noteId?: number;
  recipientUserId: number;
  status: NoteRecipientStatus;
  sentAt: string;
  /** @nullable */
  readAt: string | null;
  /** @nullable */
  archivedAt: string | null;
  recipient?: NoteUserSummary | null;
}

/**
 * A note owned by the caller (My Notes / Archive view).
 */
export interface MyNote {
  id: number;
  userId: number;
  title: string;
  content: string;
  color: string;
  isPinned: boolean;
  isArchived: boolean;
  /**
   * Owning folder id, or null for Unfiled.
   * @nullable
   */
  folderId: number | null;
  createdAt: string;
  updatedAt: string;
  labelIds: number[];
}

export interface NoteFolder {
  id: number;
  userId: number;
  name: string;
  createdAt: string;
  updatedAt: string;
  /** Number of notes in this folder, scoped to the requested tab (active vs archived). */
  noteCount: number;
}

export interface SentNote {
  id: number;
  userId: number;
  title: string;
  content: string;
  color: string;
  isPinned?: boolean;
  isArchived?: boolean;
  /**
   * Owning folder id, or null for Unfiled.
   * @nullable
   */
  folderId?: number | null;
  createdAt: string;
  updatedAt: string;
  recipients: NoteRecipientSummary[];
  replyCount: number;
}

export interface ReceivedNote {
  id: number;
  title: string;
  content: string;
  color: string;
  /**
   * Owning folder id on the recipient's side, or null for Unfiled.
   * @nullable
   */
  folderId?: number | null;
  createdAt: string;
  updatedAt: string;
  sender?: NoteUserSummary | null;
  recipientRowId: number;
  status: NoteRecipientStatus;
  sentAt: string;
  /** @nullable */
  readAt: string | null;
  /** @nullable */
  archivedAt: string | null;
}

export interface NoteReply {
  id: number;
  noteId: number;
  senderUserId: number;
  recipientUserId: number;
  content: string;
  createdAt: string;
  sender?: NoteUserSummary | null;
}

export interface NoteThread {
  id: number;
  title: string;
  content: string;
  color: string;
  /** @nullable */
  createdAt?: string | null;
  /** @nullable */
  updatedAt?: string | null;
  senderUserId: number;
  sender?: NoteUserSummary | null;
  isOwner: boolean;
  isAdmin: boolean;
  myStatus: NoteRecipientStatus | null;
  recipients: NoteRecipientSummary[];
  replies: NoteReply[];
}

export interface SendNoteResult {
  success: boolean;
  sent: number;
}

export interface UserDependentNotesPage {
  items: UserDependentNoteItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface UserDependentOrderItem {
  id: number;
  status: string;
  /** @nullable */
  notes: string | null;
  createdAt: string;
  serviceId: number;
  serviceNameAr: string;
  serviceNameEn: string;
}

export interface UserDependentOrdersPage {
  items: UserDependentOrderItem[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export interface AuditLogActor {
  id: number;
  username: string;
  /** @nullable */
  displayNameAr?: string | null;
  /** @nullable */
  displayNameEn?: string | null;
  /** @nullable */
  avatarUrl?: string | null;
}

export interface RolePermissionAuditEntry {
  id: number;
  roleId: number;
  previousPermissionIds: number[];
  newPermissionIds: number[];
  addedPermissionIds: number[];
  removedPermissionIds: number[];
  createdAt: string;
  actor: AuditLogActor | null;
}

export interface RolePermissionAuditList {
  entries: RolePermissionAuditEntry[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

export type PermissionAuditEntryTargetKind =
  (typeof PermissionAuditEntryTargetKind)[keyof typeof PermissionAuditEntryTargetKind];

export const PermissionAuditEntryTargetKind = {
  user: "user",
  group: "group",
  app: "app",
} as const;

export type PermissionAuditEntryChangeKind =
  (typeof PermissionAuditEntryChangeKind)[keyof typeof PermissionAuditEntryChangeKind];

export const PermissionAuditEntryChangeKind = {
  userroles: "user.roles",
  usergroups: "user.groups",
  groupusers: "group.users",
  grouproles: "group.roles",
  groupapps: "group.apps",
  apppermissions: "app.permissions",
} as const;

export interface PermissionAuditEntry {
  id: number;
  targetKind: PermissionAuditEntryTargetKind;
  targetId: number;
  changeKind: PermissionAuditEntryChangeKind;
  previousIds: number[];
  newIds: number[];
  addedIds: number[];
  removedIds: number[];
  createdAt: string;
  actor: AuditLogActor | null;
}

export interface PermissionAuditList {
  entries: PermissionAuditEntry[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
}

/**
 * @nullable
 */
export type AuditLogEntryMetadata = { [key: string]: unknown } | null;

export interface AuditLogEntry {
  id: number;
  action: string;
  targetType: string;
  /** @nullable */
  targetId: number | null;
  /** @nullable */
  metadata: AuditLogEntryMetadata;
  createdAt: string;
  actor: AuditLogActor | null;
}

export interface AuditLogList {
  entries: AuditLogEntry[];
  totalCount: number;
  limit: number;
  offset: number;
  /** @nullable */
  nextOffset: number | null;
  /** All distinct action names present in the audit log (for building filter UI). */
  actions: string[];
}

export type DeleteAppParams = {
  /**
   * Force deletion of an app that has dependent records (records an audit log entry).
   */
  force?: boolean;
};

export type DeleteServiceParams = {
  /**
   * Force deletion of a service that has existing orders (records an audit log entry).
   */
  force?: boolean;
};

export type GetPushVapidPublicKey200 = {
  publicKey: string;
};

export type SubscribePushBodyKeys = {
  p256dh: string;
  auth: string;
};

export type SubscribePushBody = {
  endpoint: string;
  keys: SubscribePushBodyKeys;
};

export type DeletePushSubscriptionParams = {
  /**
   * The push endpoint URL to unsubscribe
   */
  endpoint: string;
};

export type UnsubscribePushBody = {
  endpoint: string;
};

export type ListUsersParams = {
  /**
   * Free-text search across username, email, and display names
   */
  q?: string;
  /**
   * Filter to users that belong to the given group
   */
  groupId?: number;
  /**
   * Filter by active/disabled state
   */
  status?: ListUsersStatus;
};

export type ListUsersStatus =
  (typeof ListUsersStatus)[keyof typeof ListUsersStatus];

export const ListUsersStatus = {
  active: "active",
  disabled: "disabled",
} as const;

export type DeleteUserParams = {
  /**
   * Force deletion of a user that owns dependent records (records an audit log entry).
   */
  force?: boolean;
};

export type GetRolePermissionAuditParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
  /**
 * Restrict results to entries authored by the given user. Use
`0` or omit to return entries from any actor.

 * @minimum 0
 */
  actorUserId?: number;
  /**
   * Start date (inclusive, YYYY-MM-DD UTC).
   */
  from?: string;
  /**
   * End date (inclusive, YYYY-MM-DD UTC).
   */
  to?: string;
};

export type ExportRolePermissionAuditCsvParams = {
  /**
 * Restrict results to entries authored by the given user. Use
`0` or omit to return entries from any actor.

 * @minimum 0
 */
  actorUserId?: number;
  /**
   * Start date (inclusive, YYYY-MM-DD UTC).
   */
  from?: string;
  /**
   * End date (inclusive, YYYY-MM-DD UTC).
   */
  to?: string;
};

export type ListGroupsParams = {
  q?: string;
};

export type DeleteGroupParams = {
  /**
   * Force deletion of a non-empty group (records an audit log entry).
   */
  force?: boolean;
};

export type GetUserPermissionAuditParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
  /**
   * @minimum 0
   */
  actorUserId?: number;
  from?: string;
  to?: string;
};

export type GetGroupPermissionAuditParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
  /**
   * @minimum 0
   */
  actorUserId?: number;
  from?: string;
  to?: string;
};

export type GetAppPermissionAuditParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
  /**
   * @minimum 0
   */
  actorUserId?: number;
  from?: string;
  to?: string;
};

export type GetAdminStatsParams = {
  /**
   * Time range for trend stats. Use "custom" with from/to.
   */
  range?: GetAdminStatsRange;
  /**
   * Start date (inclusive, YYYY-MM-DD UTC) when range=custom
   */
  from?: string;
  /**
   * End date (inclusive, YYYY-MM-DD UTC) when range=custom
   */
  to?: string;
};

export type GetAdminStatsRange =
  (typeof GetAdminStatsRange)[keyof typeof GetAdminStatsRange];

export const GetAdminStatsRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export type GetAdminAppOpensByAppParams = {
  range?: GetAdminAppOpensByAppRange;
  from?: string;
  to?: string;
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminAppOpensByAppRange =
  (typeof GetAdminAppOpensByAppRange)[keyof typeof GetAdminAppOpensByAppRange];

export const GetAdminAppOpensByAppRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export type GetAdminAppOpensByUserParams = {
  range?: GetAdminAppOpensByUserRange;
  from?: string;
  to?: string;
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminAppOpensByUserRange =
  (typeof GetAdminAppOpensByUserRange)[keyof typeof GetAdminAppOpensByUserRange];

export const GetAdminAppOpensByUserRange = {
  "7d": "7d",
  "30d": "30d",
  "90d": "90d",
  custom: "custom",
} as const;

export type ListAuditLogsParams = {
  /**
   * Exact action name to filter by (e.g. "group.force_delete").
   */
  action?: string;
  /**
 * When true, restrict results to forced deletions only — i.e. rows
with action `group.force_delete`, `user.force_delete`,
`app.force_delete`, `service.force_delete`, or any
`group.delete`/`user.delete`/`app.delete` row whose metadata
contains `force: true`. Overrides the `action` filter when set.

 */
  forcedOnly?: boolean;
  /**
   * Start date (inclusive, YYYY-MM-DD UTC).
   */
  from?: string;
  /**
   * End date (inclusive, YYYY-MM-DD UTC).
   */
  to?: string;
  /**
 * Restrict results to entries whose `target_type` exactly matches
(e.g. "group", "app", "role", "user", "service", "settings").
Combines with `targetId` and the other filters.

 */
  targetType?: string;
  /**
 * Restrict results to entries whose `target_id` exactly matches.
Typically used together with `targetType` to focus on the history
of a single entity.

 * @minimum 1
 */
  targetId?: number;
  /**
 * Restrict results to entries written by a specific acting user
(matches `actor_user_id`). Combines with the other filters.

 * @minimum 1
 */
  actorUserId?: number;
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type ExportAuditLogsCsvParams = {
  /**
   * Exact action name to filter by (e.g. "group.force_delete").
   */
  action?: string;
  /**
 * When true, restrict the export to forced deletions only.
See the list endpoint for the exact set of matching rows.
Overrides the `action` filter when set.

 */
  forcedOnly?: boolean;
  /**
   * Start date (inclusive, YYYY-MM-DD UTC).
   */
  from?: string;
  /**
   * End date (inclusive, YYYY-MM-DD UTC).
   */
  to?: string;
  /**
 * Restrict the export to entries whose `target_type` exactly matches.
Combines with `targetId` and the other filters.

 */
  targetType?: string;
  /**
 * Restrict the export to entries whose `target_id` exactly matches.

 * @minimum 1
 */
  targetId?: number;
  /**
 * Restrict the export to entries written by a specific acting user.

 * @minimum 1
 */
  actorUserId?: number;
};

export type GetAdminAppDependentGroupsParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminAppDependentRestrictionsParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminAppDependentOpensParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminServiceDependentOrdersParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type ListMyNotesAliasParams = {
  archived?: boolean;
};

export type ListReceivedNotesParams = {
  archived?: boolean;
};

export type SendNoteBody = {
  recipientUserIds: number[];
};

export type ArchiveReceivedNoteBody = {
  archived: boolean;
};

export type ReplyToNoteBody = {
  content: string;
  /** Required when the note owner replies and there is more than one recipient. */
  recipientUserId?: number;
};

export type ListNoteFoldersParams = {
  /**
   * Tab scope for noteCount (false = My Notes, true = Archive)
   */
  archived?: boolean;
};

export type CreateNoteFolderBody = {
  /**
   * @minLength 1
   * @maxLength 80
   */
  name: string;
};

export type UpdateNoteFolderParams = {
  /**
   * Tab scope for the returned noteCount (false = My Notes, true = Archive)
   */
  archived?: boolean;
};

export type UpdateNoteFolderBody = {
  /**
   * @minLength 1
   * @maxLength 80
   */
  name: string;
};

export type GetAdminUserDependentNotesParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};

export type GetAdminUserDependentOrdersParams = {
  /**
   * @minimum 1
   * @maximum 200
   */
  limit?: number;
  /**
   * @minimum 0
   */
  offset?: number;
};
