/**
 * Generated by orval v8.5.3 🍺
 * Do not edit manually.
 * Api
 * Tx OS API specification
 * OpenAPI spec version: 0.1.0
 */
import * as zod from "zod";

/**
 * @summary Health check
 */
export const HealthCheckResponse = zod.object({
  status: zod.string(),
});

/**
 * @summary Register a new user
 */
export const registerBodyPreferredLanguageDefault = `ar`;

export const RegisterBody = zod.object({
  username: zod.string(),
  email: zod.string(),
  password: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string().default(registerBodyPreferredLanguageDefault),
});

/**
 * @summary Login
 */
export const LoginBody = zod.object({
  username: zod.string(),
  password: zod.string(),
});

export const LoginResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Logout
 */
export const LogoutResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary Request a password reset link
 */
export const ForgotPasswordBody = zod.object({
  identifier: zod.string().describe("Username or email"),
});

export const ForgotPasswordResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary Set a new password using a reset token
 */
export const resetPasswordBodyNewPasswordMin = 6;

export const ResetPasswordBody = zod.object({
  token: zod.string(),
  newPassword: zod.string().min(resetPasswordBodyNewPasswordMin),
});

export const ResetPasswordResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary Check whether a reset token is valid
 */
export const VerifyResetTokenBody = zod.object({
  token: zod.string(),
});

export const VerifyResetTokenResponse = zod.object({
  valid: zod.boolean(),
});

/**
 * @summary Admin generates a one-time password reset link for a user
 */
export const AdminIssueResetLinkParams = zod.object({
  id: zod.coerce.number(),
});

export const AdminIssueResetLinkResponse = zod.object({
  resetUrl: zod.string(),
  expiresAt: zod.coerce.date(),
});

/**
 * @summary Get current user
 */
export const GetMeResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Update preferred language
 */
export const UpdateLanguageBody = zod.object({
  language: zod.string(),
});

export const UpdateLanguageResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Update preferred home-screen clock style
 */
export const UpdateClockStyleBody = zod.object({
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .describe("Clock style; pass null to clear and use the default"),
});

export const UpdateClockStyleResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Update preferred 12/24-hour clock format
 */
export const UpdateClockHour12Body = zod.object({
  clockHour12: zod
    .boolean()
    .nullable()
    .describe(
      "Whether to use 12-hour (AM\/PM) clock format; null clears and uses the default (24-hour)",
    ),
});

export const UpdateClockHour12Response = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
});

/**
 * @summary List all active apps
 */
export const ListAppsResponseItem = zod.object({
  id: zod.number(),
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string(),
  route: zod.string(),
  color: zod.string(),
  isActive: zod.boolean(),
  isSystem: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  groupCount: zod
    .number()
    .optional()
    .describe(
      "Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
    ),
  restrictionCount: zod
    .number()
    .optional()
    .describe(
      "Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
    ),
  openCount: zod
    .number()
    .optional()
    .describe(
      "Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
    ),
});
export const ListAppsResponse = zod.array(ListAppsResponseItem);

/**
 * @summary Create a new app (admin)
 */
export const CreateAppBody = zod.object({
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string(),
  route: zod.string(),
  color: zod.string(),
  isActive: zod.boolean().optional(),
  isSystem: zod.boolean().optional(),
  sortOrder: zod.number().optional(),
  permissionIds: zod
    .array(zod.number())
    .optional()
    .describe(
      "Optional set of permission IDs to gate the new app on. Inserted with onConflictDoNothing so duplicates are tolerated.",
    ),
});

/**
 * @summary Get app by ID
 */
export const GetAppParams = zod.object({
  id: zod.coerce.number(),
});

export const GetAppResponse = zod.object({
  id: zod.number(),
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string(),
  route: zod.string(),
  color: zod.string(),
  isActive: zod.boolean(),
  isSystem: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  groupCount: zod
    .number()
    .optional()
    .describe(
      "Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
    ),
  restrictionCount: zod
    .number()
    .optional()
    .describe(
      "Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
    ),
  openCount: zod
    .number()
    .optional()
    .describe(
      "Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
    ),
});

/**
 * @summary Update app (admin)
 */
export const UpdateAppParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateAppBody = zod.object({
  nameAr: zod.string().optional(),
  nameEn: zod.string().optional(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string().optional(),
  route: zod.string().optional(),
  color: zod.string().optional(),
  isActive: zod.boolean().optional(),
  sortOrder: zod.number().optional(),
});

export const UpdateAppResponse = zod.object({
  id: zod.number(),
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string(),
  route: zod.string(),
  color: zod.string(),
  isActive: zod.boolean(),
  isSystem: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  groupCount: zod
    .number()
    .optional()
    .describe(
      "Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
    ),
  restrictionCount: zod
    .number()
    .optional()
    .describe(
      "Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
    ),
  openCount: zod
    .number()
    .optional()
    .describe(
      "Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
    ),
});

/**
 * @summary Delete app (admin)
 */
export const DeleteAppParams = zod.object({
  id: zod.coerce.number(),
});

export const deleteAppQueryForceDefault = false;

export const DeleteAppQueryParams = zod.object({
  force: zod.coerce
    .boolean()
    .default(deleteAppQueryForceDefault)
    .describe(
      "Force deletion of an app that has dependent records (records an audit log entry).",
    ),
});

/**
 * Returns the permissions currently required for users to see this app
in their launcher. An app with no rows here is unrestricted (visible
to anyone). When more than one permission is configured, holding any
one of them is sufficient.

 * @summary List the permissions that gate this app (admin)
 */
export const ListAppPermissionsParams = zod.object({
  id: zod.coerce.number(),
});

export const ListAppPermissionsResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});
export const ListAppPermissionsResponse = zod.array(
  ListAppPermissionsResponseItem,
);

/**
 * Adds a row to `app_permissions` for the (app_id, permission_id) pair.
Uses `ON CONFLICT DO NOTHING` against the composite primary key so
re-adding an existing pair is a no-op.

 * @summary Add a required permission to this app (admin)
 */
export const AddAppPermissionParams = zod.object({
  id: zod.coerce.number(),
});

export const AddAppPermissionBody = zod.object({
  permissionId: zod
    .number()
    .describe("ID of the permission to require for this app."),
});

/**
 * Given a candidate set of permission IDs that would gate this app,
returns how many users would lose visibility of the app (excluding
admins, who always see every app), the count of users who currently
see the app, and the groups that grant the app via `group_apps`. A
member of any returned group always sees the app regardless of
permission changes, so listing them lets the admin see which
populations are protected from the loss.

Mirrors `POST /roles/{id}/permissions/impact-preview` so the admin
UI can show the same kind of warning before committing the change.

 * @summary Preview the impact of changing the permissions required for an app (admin)
 */
export const PreviewAppPermissionsImpactParams = zod.object({
  id: zod.coerce.number(),
});

export const PreviewAppPermissionsImpactBody = zod.object({
  permissionIds: zod
    .array(zod.number())
    .describe(
      '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).',
    ),
});

export const PreviewAppPermissionsImpactResponse = zod.object({
  affectedUserCount: zod
    .number()
    .describe(
      "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.",
    ),
  currentlyVisibleUserCount: zod
    .number()
    .describe(
      '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\").',
    ),
  groups: zod
    .array(
      zod.object({
        id: zod.number(),
        name: zod.string(),
      }),
    )
    .describe(
      "Groups that currently grant this app via `group_apps`. Members of any of these groups keep access regardless of permission changes.",
    ),
  noChange: zod
    .boolean()
    .describe(
      "True when the candidate set is identical to the current permission set so no change would actually be saved.",
    ),
});

/**
 * @summary Remove a required permission from this app (admin)
 */
export const RemoveAppPermissionParams = zod.object({
  id: zod.coerce.number(),
  permissionId: zod.coerce.number(),
});

/**
 * @summary Log an app open event for the current user
 */
export const LogAppOpenParams = zod.object({
  id: zod.coerce.number(),
});

/**
 * @summary Set the current user's preferred home apps order
 */
export const UpdateMyAppOrderBody = zod.object({
  order: zod
    .array(zod.number())
    .describe("App IDs in the desired display order"),
});

export const UpdateMyAppOrderResponseItem = zod.object({
  id: zod.number(),
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  iconName: zod.string(),
  route: zod.string(),
  color: zod.string(),
  isActive: zod.boolean(),
  isSystem: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  groupCount: zod
    .number()
    .optional()
    .describe(
      "Number of groups granting access to this app. Populated only by the\nadmin list endpoint (GET \/admin\/apps) so the delete dialog can warn\nbefore the first click. Omitted from non-admin and single-app\nresponses.\n",
    ),
  restrictionCount: zod
    .number()
    .optional()
    .describe(
      "Number of legacy permission restrictions for this app. Populated\nonly by the admin list endpoint (GET \/admin\/apps).\n",
    ),
  openCount: zod
    .number()
    .optional()
    .describe(
      "Number of recorded app-open events for this app. Populated only by\nthe admin list endpoint (GET \/admin\/apps).\n",
    ),
});
export const UpdateMyAppOrderResponse = zod.array(UpdateMyAppOrderResponseItem);

/**
 * @summary Get application settings (public)
 */
export const GetAppSettingsResponse = zod.object({
  siteNameAr: zod.string(),
  siteNameEn: zod.string(),
  registrationOpen: zod.boolean(),
  footerTextAr: zod.string(),
  footerTextEn: zod.string(),
  updatedAt: zod.coerce.date().optional(),
});

/**
 * @summary Update application settings (admin)
 */
export const updateAppSettingsBodySiteNameArMax = 200;

export const updateAppSettingsBodySiteNameEnMax = 200;

export const updateAppSettingsBodyFooterTextArMax = 300;

export const updateAppSettingsBodyFooterTextEnMax = 300;

export const UpdateAppSettingsBody = zod.object({
  siteNameAr: zod
    .string()
    .min(1)
    .max(updateAppSettingsBodySiteNameArMax)
    .optional(),
  siteNameEn: zod
    .string()
    .min(1)
    .max(updateAppSettingsBodySiteNameEnMax)
    .optional(),
  registrationOpen: zod.boolean().optional(),
  footerTextAr: zod
    .string()
    .min(1)
    .max(updateAppSettingsBodyFooterTextArMax)
    .optional(),
  footerTextEn: zod
    .string()
    .min(1)
    .max(updateAppSettingsBodyFooterTextEnMax)
    .optional(),
});

export const UpdateAppSettingsResponse = zod.object({
  siteNameAr: zod.string(),
  siteNameEn: zod.string(),
  registrationOpen: zod.boolean(),
  footerTextAr: zod.string(),
  footerTextEn: zod.string(),
  updatedAt: zod.coerce.date().optional(),
});

/**
 * @summary Request a presigned URL for file upload
 */

export const RequestUploadUrlBody = zod.object({
  name: zod.string().min(1),
  size: zod.number().min(1),
  contentType: zod.string().min(1),
});

export const RequestUploadUrlResponse = zod.object({
  uploadURL: zod.string().url(),
  objectPath: zod.string(),
  metadata: zod
    .object({
      name: zod.string().min(1),
      size: zod.number().min(1),
      contentType: zod.string().min(1),
    })
    .optional(),
});

/**
 * @summary List all services
 */
export const ListServicesResponseItem = zod.object({
  id: zod.number(),
  categoryId: zod.number().nullish(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  imageUrl: zod.string().nullish(),
  price: zod.string().nullish(),
  isAvailable: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
    ),
});
export const ListServicesResponse = zod.array(ListServicesResponseItem);

/**
 * @summary Create a service (admin)
 */
export const CreateServiceBody = zod.object({
  categoryId: zod.number().nullish(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  imageUrl: zod.string().nullish(),
  price: zod.string().nullish(),
  isAvailable: zod.boolean().optional(),
  sortOrder: zod.number().optional(),
});

/**
 * @summary Get service by ID
 */
export const GetServiceParams = zod.object({
  id: zod.coerce.number(),
});

export const GetServiceResponse = zod.object({
  id: zod.number(),
  categoryId: zod.number().nullish(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  imageUrl: zod.string().nullish(),
  price: zod.string().nullish(),
  isAvailable: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
    ),
});

/**
 * @summary Update service (admin)
 */
export const UpdateServiceParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateServiceBody = zod.object({
  categoryId: zod.number().nullish(),
  nameAr: zod.string().optional(),
  nameEn: zod.string().optional(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  imageUrl: zod.string().nullish(),
  price: zod.string().nullish(),
  isAvailable: zod.boolean().optional(),
  sortOrder: zod.number().optional(),
});

export const UpdateServiceResponse = zod.object({
  id: zod.number(),
  categoryId: zod.number().nullish(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  imageUrl: zod.string().nullish(),
  price: zod.string().nullish(),
  isAvailable: zod.boolean(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service_orders rows referencing this service. Populated\nonly by the list endpoint (GET \/services) so the admin delete\ndialog can warn before the first click. Omitted from single-service\nresponses.\n",
    ),
});

/**
 * @summary Delete service (admin)
 */
export const DeleteServiceParams = zod.object({
  id: zod.coerce.number(),
});

export const deleteServiceQueryForceDefault = false;

export const DeleteServiceQueryParams = zod.object({
  force: zod.coerce
    .boolean()
    .default(deleteServiceQueryForceDefault)
    .describe(
      "Force deletion of a service that has existing orders (records an audit log entry).",
    ),
});

/**
 * @summary Place a service order
 */
export const createServiceOrderBodyNotesMax = 500;

export const CreateServiceOrderBody = zod.object({
  serviceId: zod.number(),
  notes: zod.string().max(createServiceOrderBodyNotesMax).nullish(),
});

/**
 * @summary List orders placed by the current user
 */
export const ListMyServiceOrdersResponseItem = zod.object({
  id: zod.number(),
  serviceId: zod.number(),
  userId: zod.number(),
  assignedTo: zod.number().nullish(),
  notes: zod.string().nullish(),
  status: zod.enum([
    "pending",
    "received",
    "preparing",
    "completed",
    "cancelled",
  ]),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  service: zod.object({
    id: zod.number(),
    nameAr: zod.string(),
    nameEn: zod.string(),
    imageUrl: zod.string().nullish(),
  }),
  requester: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
  assignee: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
});
export const ListMyServiceOrdersResponse = zod.array(
  ListMyServiceOrdersResponseItem,
);

/**
 * @summary List active orders for receivers (requires orders.receive permission). Returns pending+unassigned orders plus orders assigned to me that are not completed/cancelled.
 */
export const ListIncomingServiceOrdersResponseItem = zod.object({
  id: zod.number(),
  serviceId: zod.number(),
  userId: zod.number(),
  assignedTo: zod.number().nullish(),
  notes: zod.string().nullish(),
  status: zod.enum([
    "pending",
    "received",
    "preparing",
    "completed",
    "cancelled",
  ]),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  service: zod.object({
    id: zod.number(),
    nameAr: zod.string(),
    nameEn: zod.string(),
    imageUrl: zod.string().nullish(),
  }),
  requester: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
  assignee: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
});
export const ListIncomingServiceOrdersResponse = zod.array(
  ListIncomingServiceOrdersResponseItem,
);

/**
 * @summary Update order status. Preparing/completed restricted to assigned receiver or admin; cancelled allowed for owner (while pending|received) or admin (any time); pending/received are restore-from-cancelled transitions allowed for owner or admin.
 */
export const UpdateServiceOrderStatusParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateServiceOrderStatusBody = zod.object({
  status: zod.enum([
    "pending",
    "received",
    "preparing",
    "completed",
    "cancelled",
  ]),
});

export const UpdateServiceOrderStatusResponse = zod.object({
  id: zod.number(),
  serviceId: zod.number(),
  userId: zod.number(),
  assignedTo: zod.number().nullish(),
  notes: zod.string().nullish(),
  status: zod.enum([
    "pending",
    "received",
    "preparing",
    "completed",
    "cancelled",
  ]),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  service: zod.object({
    id: zod.number(),
    nameAr: zod.string(),
    nameEn: zod.string(),
    imageUrl: zod.string().nullish(),
  }),
  requester: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
  assignee: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
});

/**
 * @summary Receiver atomically claims a pending order (sets status=received, assigned_to=current user). Requires orders.receive permission. Returns 409 already_claimed if the order is no longer pending+unassigned.
 */
export const ConfirmServiceOrderReceiptParams = zod.object({
  id: zod.coerce.number(),
});

export const ConfirmServiceOrderReceiptResponse = zod.object({
  id: zod.number(),
  serviceId: zod.number(),
  userId: zod.number(),
  assignedTo: zod.number().nullish(),
  notes: zod.string().nullish(),
  status: zod.enum([
    "pending",
    "received",
    "preparing",
    "completed",
    "cancelled",
  ]),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
  service: zod.object({
    id: zod.number(),
    nameAr: zod.string(),
    nameEn: zod.string(),
    imageUrl: zod.string().nullish(),
  }),
  requester: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
  assignee: zod
    .union([
      zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
      }),
      zod.null(),
    ])
    .optional(),
});

/**
 * @summary List service categories
 */
export const ListServiceCategoriesResponseItem = zod.object({
  id: zod.number(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  sortOrder: zod.number(),
  createdAt: zod.coerce.date(),
});
export const ListServiceCategoriesResponse = zod.array(
  ListServiceCategoriesResponseItem,
);

/**
 * @summary List conversations for current user
 */
export const ListConversationsResponseItem = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});
export const ListConversationsResponse = zod.array(
  ListConversationsResponseItem,
);

/**
 * @summary Create a conversation
 */
export const CreateConversationBody = zod.object({
  participantIds: zod.array(zod.number()),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean().optional(),
});

/**
 * @summary Get conversation by ID
 */
export const GetConversationParams = zod.object({
  id: zod.coerce.number(),
});

export const GetConversationResponse = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});

/**
 * @summary Update conversation properties (admin only)
 */
export const UpdateConversationParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateConversationBody = zod.object({
  avatarUrl: zod.string().nullish(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
});

export const UpdateConversationResponse = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});

/**
 * @summary Add participants to a group conversation (admin only)
 */
export const AddConversationParticipantsParams = zod.object({
  id: zod.coerce.number(),
});

export const AddConversationParticipantsBody = zod.object({
  userIds: zod.array(zod.number()),
});

export const AddConversationParticipantsResponse = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});

/**
 * @summary Remove a participant from a group conversation (admin only)
 */
export const RemoveConversationParticipantParams = zod.object({
  id: zod.coerce.number(),
  userId: zod.coerce.number(),
});

export const RemoveConversationParticipantResponse = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});

/**
 * @summary Update per-user state (mute, archive)
 */
export const UpdateConversationStateParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateConversationStateBody = zod.object({
  isMuted: zod.boolean().optional(),
  isArchived: zod.boolean().optional(),
});

export const UpdateConversationStateResponse = zod.object({
  id: zod.number(),
  nameAr: zod.string().nullish(),
  nameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  isGroup: zod.boolean(),
  createdBy: zod.number(),
  participants: zod.array(
    zod.object({
      id: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      isAdmin: zod.boolean(),
    }),
  ),
  lastMessage: zod
    .object({
      id: zod.number(),
      conversationId: zod.number(),
      senderId: zod.number(),
      content: zod.string(),
      kind: zod.enum([
        "user",
        "group_renamed",
        "members_added",
        "member_removed",
        "member_left",
        "admin_promoted",
      ]),
      meta: zod.record(zod.string(), zod.unknown()).nullish(),
      sender: zod.object({
        id: zod.number(),
        username: zod.string(),
        displayNameAr: zod.string().nullish(),
        displayNameEn: zod.string().nullish(),
        avatarUrl: zod.string().nullish(),
        isAdmin: zod.boolean(),
      }),
      createdAt: zod.coerce.date(),
      updatedAt: zod.coerce.date(),
    })
    .nullish(),
  unreadCount: zod.number(),
  isMuted: zod.boolean(),
  isArchived: zod.boolean(),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});

/**
 * @summary Leave a group conversation
 */
export const LeaveConversationParams = zod.object({
  id: zod.coerce.number(),
});

export const LeaveConversationBody = zod.object({
  successorId: zod
    .number()
    .nullish()
    .describe(
      "When the leaver is the only admin, optionally name a specific\nremaining member to promote. Omit (or send null) to keep the\nautomatic behavior of promoting the longest-tenured member.\n",
    ),
});

export const LeaveConversationResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary List messages in a conversation
 */
export const ListMessagesParams = zod.object({
  id: zod.coerce.number(),
});

export const ListMessagesResponseItem = zod.object({
  id: zod.number(),
  conversationId: zod.number(),
  senderId: zod.number(),
  content: zod.string(),
  kind: zod.enum([
    "user",
    "group_renamed",
    "members_added",
    "member_removed",
    "member_left",
    "admin_promoted",
  ]),
  meta: zod.record(zod.string(), zod.unknown()).nullish(),
  sender: zod.object({
    id: zod.number(),
    username: zod.string(),
    displayNameAr: zod.string().nullish(),
    displayNameEn: zod.string().nullish(),
    avatarUrl: zod.string().nullish(),
    isAdmin: zod.boolean(),
  }),
  createdAt: zod.coerce.date(),
  updatedAt: zod.coerce.date(),
});
export const ListMessagesResponse = zod.array(ListMessagesResponseItem);

/**
 * @summary Send a message
 */
export const SendMessageParams = zod.object({
  id: zod.coerce.number(),
});

export const SendMessageBody = zod.object({
  content: zod.string(),
});

/**
 * @summary Mark all messages in conversation as read
 */
export const MarkConversationReadParams = zod.object({
  id: zod.coerce.number(),
});

export const MarkConversationReadResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary List notifications for current user
 */
export const ListNotificationsResponseItem = zod.object({
  id: zod.number(),
  userId: zod.number(),
  titleAr: zod.string(),
  titleEn: zod.string(),
  bodyAr: zod.string().nullish(),
  bodyEn: zod.string().nullish(),
  type: zod.string(),
  isRead: zod.boolean(),
  relatedId: zod.number().nullish(),
  relatedType: zod.string().nullish(),
  createdAt: zod.coerce.date(),
});
export const ListNotificationsResponse = zod.array(
  ListNotificationsResponseItem,
);

/**
 * @summary Mark notification as read
 */
export const MarkNotificationReadParams = zod.object({
  id: zod.coerce.number(),
});

export const MarkNotificationReadResponse = zod.object({
  id: zod.number(),
  userId: zod.number(),
  titleAr: zod.string(),
  titleEn: zod.string(),
  bodyAr: zod.string().nullish(),
  bodyEn: zod.string().nullish(),
  type: zod.string(),
  isRead: zod.boolean(),
  relatedId: zod.number().nullish(),
  relatedType: zod.string().nullish(),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Mark all notifications as read
 */
export const MarkAllNotificationsReadResponse = zod.object({
  success: zod.boolean(),
});

/**
 * @summary List all users (admin)
 */
export const ListUsersQueryParams = zod.object({
  q: zod.coerce
    .string()
    .optional()
    .describe("Free-text search across username, email, and display names"),
  groupId: zod.coerce
    .number()
    .optional()
    .describe("Filter to users that belong to the given group"),
  status: zod
    .enum(["active", "disabled"])
    .optional()
    .describe("Filter by active\/disabled state"),
});

export const ListUsersResponseItem = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
  noteCount: zod
    .number()
    .optional()
    .describe(
      "Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
    ),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  conversationCount: zod
    .number()
    .optional()
    .describe(
      "Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  messageCount: zod
    .number()
    .optional()
    .describe(
      "Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
});
export const ListUsersResponse = zod.array(ListUsersResponseItem);

/**
 * @summary Create a user (admin)
 */
export const createUserBodyPreferredLanguageDefault = `ar`;

export const CreateUserBody = zod.object({
  username: zod.string(),
  email: zod.string(),
  password: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod
    .string()
    .default(createUserBodyPreferredLanguageDefault),
  groupIds: zod
    .array(zod.number())
    .optional()
    .describe(
      "Initial group memberships in addition to the auto-assigned Everyone group.",
    ),
});

/**
 * @summary Get user by ID (admin)
 */
export const GetUserParams = zod.object({
  id: zod.coerce.number(),
});

export const GetUserResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
  noteCount: zod
    .number()
    .optional()
    .describe(
      "Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
    ),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  conversationCount: zod
    .number()
    .optional()
    .describe(
      "Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  messageCount: zod
    .number()
    .optional()
    .describe(
      "Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
});

/**
 * @summary Update user (admin)
 */
export const UpdateUserParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateUserBody = zod.object({
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  isActive: zod.boolean().optional(),
  preferredLanguage: zod.string().optional(),
  groupIds: zod
    .array(zod.number())
    .optional()
    .describe("If provided, replaces the user's group memberships"),
});

export const UpdateUserResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
  noteCount: zod
    .number()
    .optional()
    .describe(
      "Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
    ),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  conversationCount: zod
    .number()
    .optional()
    .describe(
      "Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  messageCount: zod
    .number()
    .optional()
    .describe(
      "Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
});

/**
 * @summary Delete user (admin)
 */
export const DeleteUserParams = zod.object({
  id: zod.coerce.number(),
});

export const deleteUserQueryForceDefault = false;

export const DeleteUserQueryParams = zod.object({
  force: zod.coerce
    .boolean()
    .default(deleteUserQueryForceDefault)
    .describe(
      "Force deletion of a user that owns dependent records (records an audit log entry).",
    ),
});

/**
 * Permanently deletes the order. Allowed when the caller owns the order
AND it is in a terminal status (completed/cancelled), OR the caller is
an admin (any status).

 * @summary Delete a service order
 */
export const DeleteServiceOrderParams = zod.object({
  id: zod.coerce.number(),
});

/**
 * @summary Idempotently add a role to a user (admin)
 */
export const AddUserRoleParams = zod.object({
  id: zod.coerce.number(),
});

export const AddUserRoleBody = zod.object({
  roleName: zod.string().min(1),
});

export const AddUserRoleResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
  noteCount: zod
    .number()
    .optional()
    .describe(
      "Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
    ),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  conversationCount: zod
    .number()
    .optional()
    .describe(
      "Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  messageCount: zod
    .number()
    .optional()
    .describe(
      "Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
});

/**
 * @summary Idempotently remove a role from a user (admin)
 */
export const RemoveUserRoleParams = zod.object({
  id: zod.coerce.number(),
  roleName: zod.coerce.string(),
});

export const RemoveUserRoleResponse = zod.object({
  id: zod.number(),
  username: zod.string(),
  email: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  preferredLanguage: zod.string(),
  clockStyle: zod
    .union([
      zod
        .enum(["full", "digital", "digital-no-seconds", "analog", "minimal"])
        .describe("Per-user home-screen clock style preference"),
      zod.null(),
    ])
    .optional(),
  clockHour12: zod.boolean().nullish(),
  avatarUrl: zod.string().nullish(),
  isActive: zod.boolean(),
  roles: zod.array(zod.string()),
  groups: zod.array(
    zod.object({
      id: zod.number(),
      name: zod.string(),
      descriptionAr: zod.string().nullish(),
      descriptionEn: zod.string().nullish(),
    }),
  ),
  createdAt: zod.coerce.date(),
  noteCount: zod
    .number()
    .optional()
    .describe(
      "Dependent record counts. Populated only by the admin list endpoint\n(GET \/users) so the delete dialog can warn before the first click.\nOmitted from single-user responses (GET \/users\/:id, PATCH, etc).\n",
    ),
  orderCount: zod
    .number()
    .optional()
    .describe(
      "Number of service orders owned by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  conversationCount: zod
    .number()
    .optional()
    .describe(
      "Number of conversations created by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
  messageCount: zod
    .number()
    .optional()
    .describe(
      "Number of messages authored by this user. Populated only by the\nadmin list endpoint (GET \/users).\n",
    ),
});

/**
 * @summary List all available permissions (admin)
 */
export const ListPermissionsResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});
export const ListPermissionsResponse = zod.array(ListPermissionsResponseItem);

/**
 * @summary List roles (admin)
 */
export const ListRolesResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  isSystem: zod.boolean(),
  createdAt: zod.coerce.date(),
});
export const ListRolesResponse = zod.array(ListRolesResponseItem);

/**
 * @summary Create role (admin)
 */

export const CreateRoleBody = zod.object({
  name: zod.string().min(1),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});

/**
 * @summary Update role name and descriptions (admin)
 */
export const UpdateRoleParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateRoleBody = zod.object({
  name: zod.string().min(1).optional(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});

export const UpdateRoleResponse = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  isSystem: zod.boolean(),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Delete role (admin)
 */
export const DeleteRoleParams = zod.object({
  id: zod.coerce.number(),
});

/**
 * @summary Count users and groups currently assigned to a role (admin)
 */
export const GetRoleUsageParams = zod.object({
  id: zod.coerce.number(),
});

export const GetRoleUsageResponse = zod.object({
  userCount: zod.number(),
  groupCount: zod.number(),
});

/**
 * Returns permission-change audit records for the given role, newest
first. Supports offset-based pagination via `limit`/`offset` and
optional filters by acting user (`actorUserId`) and a date range
(`from`/`to`, inclusive UTC days). The response includes the total
matching count and the next offset to load (or `null` when the end
has been reached). Each entry captures the actor (if known), the
previous permission IDs, the new permission IDs, and the timestamp.

 * @summary List recent permission-change audit entries for a role (admin)
 */
export const GetRolePermissionAuditParams = zod.object({
  id: zod.coerce.number(),
});

export const getRolePermissionAuditQueryLimitDefault = 10;
export const getRolePermissionAuditQueryLimitMax = 200;

export const getRolePermissionAuditQueryOffsetDefault = 0;
export const getRolePermissionAuditQueryOffsetMin = 0;

export const getRolePermissionAuditQueryActorUserIdMin = 0;

export const GetRolePermissionAuditQueryParams = zod.object({
  limit: zod.coerce
    .number()
    .min(1)
    .max(getRolePermissionAuditQueryLimitMax)
    .default(getRolePermissionAuditQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getRolePermissionAuditQueryOffsetMin)
    .default(getRolePermissionAuditQueryOffsetDefault),
  actorUserId: zod.coerce
    .number()
    .min(getRolePermissionAuditQueryActorUserIdMin)
    .optional()
    .describe(
      "Restrict results to entries authored by the given user. Use\n`0` or omit to return entries from any actor.\n",
    ),
  from: zod
    .date()
    .optional()
    .describe("Start date (inclusive, YYYY-MM-DD UTC)."),
  to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
});

export const GetRolePermissionAuditResponse = zod.object({
  entries: zod.array(
    zod.object({
      id: zod.number(),
      roleId: zod.number(),
      previousPermissionIds: zod.array(zod.number()),
      newPermissionIds: zod.array(zod.number()),
      addedPermissionIds: zod.array(zod.number()),
      removedPermissionIds: zod.array(zod.number()),
      createdAt: zod.coerce.date(),
      actor: zod.union([
        zod.object({
          id: zod.number(),
          username: zod.string(),
          displayNameAr: zod.string().nullish(),
          displayNameEn: zod.string().nullish(),
          avatarUrl: zod.string().nullish(),
        }),
        zod.null(),
      ]),
    }),
  ),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
});

/**
 * Given a candidate set of permission IDs for the role, returns the list of
permissions that would be removed and, for each one, how many users would
lose the permission (because they hold this role directly or via a group
and have no other role granting the permission), plus the groups that
currently grant this role.

 * @summary Preview the impact of changing a role's permission set (admin)
 */
export const PreviewRolePermissionsImpactParams = zod.object({
  id: zod.coerce.number(),
});

export const PreviewRolePermissionsImpactBody = zod.object({
  permissionIds: zod
    .array(zod.number())
    .describe(
      "Candidate set of permission IDs to evaluate against the role's current assignments.",
    ),
});

export const PreviewRolePermissionsImpactResponse = zod.object({
  removed: zod
    .array(
      zod.object({
        permissionId: zod.number(),
        permissionName: zod.string(),
        userCount: zod
          .number()
          .describe(
            "Distinct users who currently have this permission via this role and would lose it (no other role they hold grants the permission).",
          ),
        groupCount: zod
          .number()
          .describe("Number of groups that currently grant this role."),
        groups: zod
          .array(
            zod.object({
              id: zod.number(),
              name: zod.string(),
            }),
          )
          .describe(
            "Groups that currently grant this role and therefore propagate the removal to their members.",
          ),
      }),
    )
    .describe(
      "One entry per permission that is currently assigned to the role but not in the candidate set.",
    ),
  totalAffectedUsers: zod
    .number()
    .describe(
      "Distinct users that would lose at least one permission across all removals.",
    ),
});

/**
 * @summary List permissions assigned to a role (admin)
 */
export const GetRolePermissionsParams = zod.object({
  id: zod.coerce.number(),
});

export const GetRolePermissionsResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});
export const GetRolePermissionsResponse = zod.array(
  GetRolePermissionsResponseItem,
);

/**
 * @summary Atomically replace the permissions assigned to a role (admin)
 */
export const ReplaceRolePermissionsParams = zod.object({
  id: zod.coerce.number(),
});

export const ReplaceRolePermissionsBody = zod.object({
  permissionIds: zod
    .array(zod.number())
    .describe(
      "Full set of permission IDs the role should grant. Existing assignments not present here are removed.",
    ),
});

export const ReplaceRolePermissionsResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
});
export const ReplaceRolePermissionsResponse = zod.array(
  ReplaceRolePermissionsResponseItem,
);

/**
 * @summary List groups (admin)
 */
export const ListGroupsQueryParams = zod.object({
  q: zod.coerce.string().optional(),
});

export const ListGroupsResponseItem = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  isSystem: zod.boolean(),
  memberCount: zod.number(),
  appCount: zod.number(),
  roleCount: zod.number(),
  createdAt: zod.coerce.date(),
});
export const ListGroupsResponse = zod.array(ListGroupsResponseItem);

/**
 * @summary Create group (admin)
 */

export const CreateGroupBody = zod.object({
  name: zod.string().min(1),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  appIds: zod.array(zod.number()).optional(),
  roleIds: zod.array(zod.number()).optional(),
  userIds: zod.array(zod.number()).optional(),
});

/**
 * @summary Get group with members and apps (admin)
 */
export const GetGroupParams = zod.object({
  id: zod.coerce.number(),
});

export const GetGroupResponse = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  isSystem: zod.boolean(),
  appIds: zod.array(zod.number()),
  roleIds: zod.array(zod.number()),
  userIds: zod.array(zod.number()),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Update group (admin)
 */
export const UpdateGroupParams = zod.object({
  id: zod.coerce.number(),
});

export const UpdateGroupBody = zod.object({
  name: zod.string().min(1).optional(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  appIds: zod.array(zod.number()).optional(),
  roleIds: zod.array(zod.number()).optional(),
  userIds: zod.array(zod.number()).optional(),
});

export const UpdateGroupResponse = zod.object({
  id: zod.number(),
  name: zod.string(),
  descriptionAr: zod.string().nullish(),
  descriptionEn: zod.string().nullish(),
  isSystem: zod.boolean(),
  appIds: zod.array(zod.number()),
  roleIds: zod.array(zod.number()),
  userIds: zod.array(zod.number()),
  createdAt: zod.coerce.date(),
});

/**
 * @summary Delete group (admin)
 */
export const DeleteGroupParams = zod.object({
  id: zod.coerce.number(),
});

export const deleteGroupQueryForceDefault = false;

export const DeleteGroupQueryParams = zod.object({
  force: zod.coerce
    .boolean()
    .default(deleteGroupQueryForceDefault)
    .describe(
      "Force deletion of a non-empty group (records an audit log entry).",
    ),
});

/**
 * Returns permission-change audit records for the given user, newest
first. Mirrors the role audit endpoint shape and supports the same
`limit`/`offset`/`actorUserId`/`from`/`to` filters. Each entry
captures the actor (if known), the previous and new id sets, and
the change kind (`user.roles` or `user.groups`).

 * @summary List recent permission-change audit entries for a user (admin)
 */
export const GetUserPermissionAuditParams = zod.object({
  id: zod.coerce.number(),
});

export const getUserPermissionAuditQueryLimitDefault = 10;
export const getUserPermissionAuditQueryLimitMax = 200;

export const getUserPermissionAuditQueryOffsetDefault = 0;
export const getUserPermissionAuditQueryOffsetMin = 0;

export const getUserPermissionAuditQueryActorUserIdMin = 0;

export const GetUserPermissionAuditQueryParams = zod.object({
  limit: zod.coerce
    .number()
    .min(1)
    .max(getUserPermissionAuditQueryLimitMax)
    .default(getUserPermissionAuditQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getUserPermissionAuditQueryOffsetMin)
    .default(getUserPermissionAuditQueryOffsetDefault),
  actorUserId: zod.coerce
    .number()
    .min(getUserPermissionAuditQueryActorUserIdMin)
    .optional(),
  from: zod.date().optional(),
  to: zod.date().optional(),
});

export const GetUserPermissionAuditResponse = zod.object({
  entries: zod.array(
    zod.object({
      id: zod.number(),
      targetKind: zod.enum(["user", "group", "app"]),
      targetId: zod.number(),
      changeKind: zod.enum([
        "user.roles",
        "user.groups",
        "group.users",
        "group.roles",
        "group.apps",
        "app.permissions",
      ]),
      previousIds: zod.array(zod.number()),
      newIds: zod.array(zod.number()),
      addedIds: zod.array(zod.number()),
      removedIds: zod.array(zod.number()),
      createdAt: zod.coerce.date(),
      actor: zod.union([
        zod.object({
          id: zod.number(),
          username: zod.string(),
          displayNameAr: zod.string().nullish(),
          displayNameEn: zod.string().nullish(),
          avatarUrl: zod.string().nullish(),
        }),
        zod.null(),
      ]),
    }),
  ),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
});

/**
 * Returns permission-change audit records for the given group, newest
first. Captures changes to the group's user, role and app sets via
the discriminator field `changeKind`.

 * @summary List recent permission-change audit entries for a group (admin)
 */
export const GetGroupPermissionAuditParams = zod.object({
  id: zod.coerce.number(),
});

export const getGroupPermissionAuditQueryLimitDefault = 10;
export const getGroupPermissionAuditQueryLimitMax = 200;

export const getGroupPermissionAuditQueryOffsetDefault = 0;
export const getGroupPermissionAuditQueryOffsetMin = 0;

export const getGroupPermissionAuditQueryActorUserIdMin = 0;

export const GetGroupPermissionAuditQueryParams = zod.object({
  limit: zod.coerce
    .number()
    .min(1)
    .max(getGroupPermissionAuditQueryLimitMax)
    .default(getGroupPermissionAuditQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getGroupPermissionAuditQueryOffsetMin)
    .default(getGroupPermissionAuditQueryOffsetDefault),
  actorUserId: zod.coerce
    .number()
    .min(getGroupPermissionAuditQueryActorUserIdMin)
    .optional(),
  from: zod.date().optional(),
  to: zod.date().optional(),
});

export const GetGroupPermissionAuditResponse = zod.object({
  entries: zod.array(
    zod.object({
      id: zod.number(),
      targetKind: zod.enum(["user", "group", "app"]),
      targetId: zod.number(),
      changeKind: zod.enum([
        "user.roles",
        "user.groups",
        "group.users",
        "group.roles",
        "group.apps",
        "app.permissions",
      ]),
      previousIds: zod.array(zod.number()),
      newIds: zod.array(zod.number()),
      addedIds: zod.array(zod.number()),
      removedIds: zod.array(zod.number()),
      createdAt: zod.coerce.date(),
      actor: zod.union([
        zod.object({
          id: zod.number(),
          username: zod.string(),
          displayNameAr: zod.string().nullish(),
          displayNameEn: zod.string().nullish(),
          avatarUrl: zod.string().nullish(),
        }),
        zod.null(),
      ]),
    }),
  ),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
});

/**
 * Returns permission-change audit records for the given app, newest
first. Captures changes to the set of permissions required to open
the app (`changeKind: app.permissions`).

 * @summary List recent permission-change audit entries for an app (admin)
 */
export const GetAppPermissionAuditParams = zod.object({
  id: zod.coerce.number(),
});

export const getAppPermissionAuditQueryLimitDefault = 10;
export const getAppPermissionAuditQueryLimitMax = 200;

export const getAppPermissionAuditQueryOffsetDefault = 0;
export const getAppPermissionAuditQueryOffsetMin = 0;

export const getAppPermissionAuditQueryActorUserIdMin = 0;

export const GetAppPermissionAuditQueryParams = zod.object({
  limit: zod.coerce
    .number()
    .min(1)
    .max(getAppPermissionAuditQueryLimitMax)
    .default(getAppPermissionAuditQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getAppPermissionAuditQueryOffsetMin)
    .default(getAppPermissionAuditQueryOffsetDefault),
  actorUserId: zod.coerce
    .number()
    .min(getAppPermissionAuditQueryActorUserIdMin)
    .optional(),
  from: zod.date().optional(),
  to: zod.date().optional(),
});

export const GetAppPermissionAuditResponse = zod.object({
  entries: zod.array(
    zod.object({
      id: zod.number(),
      targetKind: zod.enum(["user", "group", "app"]),
      targetId: zod.number(),
      changeKind: zod.enum([
        "user.roles",
        "user.groups",
        "group.users",
        "group.roles",
        "group.apps",
        "app.permissions",
      ]),
      previousIds: zod.array(zod.number()),
      newIds: zod.array(zod.number()),
      addedIds: zod.array(zod.number()),
      removedIds: zod.array(zod.number()),
      createdAt: zod.coerce.date(),
      actor: zod.union([
        zod.object({
          id: zod.number(),
          username: zod.string(),
          displayNameAr: zod.string().nullish(),
          displayNameEn: zod.string().nullish(),
          avatarUrl: zod.string().nullish(),
        }),
        zod.null(),
      ]),
    }),
  ),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
});

/**
 * @summary Get home screen stats
 */
export const GetHomeStatsResponse = zod.object({
  totalApps: zod.number(),
  totalServices: zod.number(),
  unreadNotifications: zod.number(),
  unreadMessages: zod.number(),
  totalUsers: zod.number(),
});

/**
 * @summary Get admin dashboard trend stats
 */
export const getAdminStatsQueryRangeDefault = `7d`;

export const GetAdminStatsQueryParams = zod.object({
  range: zod
    .enum(["7d", "30d", "90d", "custom"])
    .default(getAdminStatsQueryRangeDefault)
    .describe('Time range for trend stats. Use \"custom\" with from\/to.'),
  from: zod
    .date()
    .optional()
    .describe("Start date (inclusive, YYYY-MM-DD UTC) when range=custom"),
  to: zod
    .date()
    .optional()
    .describe("End date (inclusive, YYYY-MM-DD UTC) when range=custom"),
});

export const GetAdminStatsResponse = zod.object({
  range: zod.enum(["7d", "30d", "90d", "custom"]),
  rangeDays: zod.number(),
  rangeFrom: zod
    .string()
    .optional()
    .describe("Start date (YYYY-MM-DD UTC) of the selected window"),
  rangeTo: zod
    .string()
    .optional()
    .describe("End date (YYYY-MM-DD UTC) of the selected window"),
  newUsersInRange: zod.number(),
  newUsersPrevRange: zod.number(),
  activeServices: zod.number(),
  inactiveServices: zod.number(),
  signupsByDay: zod.array(
    zod.object({
      date: zod.string(),
      count: zod.number(),
    }),
  ),
  appOpensByDay: zod.array(
    zod.object({
      date: zod.string(),
      count: zod.number(),
    }),
  ),
  appOpensInRange: zod.number(),
  appOpensPrevRange: zod.number(),
  servicesCreatedByDay: zod.array(
    zod.object({
      date: zod.string(),
      count: zod.number(),
    }),
  ),
  servicesCreatedInRange: zod.number(),
  topApps: zod.array(
    zod.object({
      appId: zod.number(),
      slug: zod.string(),
      nameAr: zod.string(),
      nameEn: zod.string(),
      iconName: zod.string(),
      color: zod.string(),
      count: zod.number(),
    }),
  ),
  mostActiveUsers: zod.array(
    zod.object({
      userId: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
      count: zod.number(),
    }),
  ),
});

/**
 * @summary Recent opens of a single app within the selected range
 */
export const GetAdminAppOpensByAppParams = zod.object({
  appId: zod.coerce.number(),
});

export const getAdminAppOpensByAppQueryRangeDefault = `7d`;
export const getAdminAppOpensByAppQueryLimitDefault = 100;
export const getAdminAppOpensByAppQueryLimitMax = 200;

export const getAdminAppOpensByAppQueryOffsetDefault = 0;
export const getAdminAppOpensByAppQueryOffsetMin = 0;

export const GetAdminAppOpensByAppQueryParams = zod.object({
  range: zod
    .enum(["7d", "30d", "90d", "custom"])
    .default(getAdminAppOpensByAppQueryRangeDefault),
  from: zod.date().optional(),
  to: zod.date().optional(),
  limit: zod.coerce
    .number()
    .min(1)
    .max(getAdminAppOpensByAppQueryLimitMax)
    .default(getAdminAppOpensByAppQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getAdminAppOpensByAppQueryOffsetMin)
    .default(getAdminAppOpensByAppQueryOffsetDefault),
});

export const GetAdminAppOpensByAppResponse = zod.object({
  appId: zod.number(),
  slug: zod.string(),
  nameAr: zod.string(),
  nameEn: zod.string(),
  iconName: zod.string(),
  color: zod.string(),
  range: zod.enum(["7d", "30d", "90d", "custom"]),
  rangeDays: zod.number(),
  rangeFrom: zod.string(),
  rangeTo: zod.string(),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
  opens: zod.array(
    zod.object({
      id: zod.number(),
      createdAt: zod.coerce.date(),
      userId: zod.number(),
      username: zod.string(),
      displayNameAr: zod.string().nullish(),
      displayNameEn: zod.string().nullish(),
      avatarUrl: zod.string().nullish(),
    }),
  ),
});

/**
 * @summary Recent app opens by a single user within the selected range
 */
export const GetAdminAppOpensByUserParams = zod.object({
  userId: zod.coerce.number(),
});

export const getAdminAppOpensByUserQueryRangeDefault = `7d`;
export const getAdminAppOpensByUserQueryLimitDefault = 100;
export const getAdminAppOpensByUserQueryLimitMax = 200;

export const getAdminAppOpensByUserQueryOffsetDefault = 0;
export const getAdminAppOpensByUserQueryOffsetMin = 0;

export const GetAdminAppOpensByUserQueryParams = zod.object({
  range: zod
    .enum(["7d", "30d", "90d", "custom"])
    .default(getAdminAppOpensByUserQueryRangeDefault),
  from: zod.date().optional(),
  to: zod.date().optional(),
  limit: zod.coerce
    .number()
    .min(1)
    .max(getAdminAppOpensByUserQueryLimitMax)
    .default(getAdminAppOpensByUserQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(getAdminAppOpensByUserQueryOffsetMin)
    .default(getAdminAppOpensByUserQueryOffsetDefault),
});

export const GetAdminAppOpensByUserResponse = zod.object({
  userId: zod.number(),
  username: zod.string(),
  displayNameAr: zod.string().nullish(),
  displayNameEn: zod.string().nullish(),
  avatarUrl: zod.string().nullish(),
  range: zod.enum(["7d", "30d", "90d", "custom"]),
  rangeDays: zod.number(),
  rangeFrom: zod.string(),
  rangeTo: zod.string(),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
  opens: zod.array(
    zod.object({
      id: zod.number(),
      createdAt: zod.coerce.date(),
      appId: zod.number(),
      slug: zod.string(),
      nameAr: zod.string(),
      nameEn: zod.string(),
      iconName: zod.string(),
      color: zod.string(),
    }),
  ),
});

/**
 * Returns recent entries from the audit log, newest first. Admin only.
Filter by action (exact match) and/or a date range (inclusive).

 * @summary List audit log entries (admin)
 */
export const listAuditLogsQueryForcedOnlyDefault = false;
export const listAuditLogsQueryLimitDefault = 50;
export const listAuditLogsQueryLimitMax = 200;

export const listAuditLogsQueryOffsetDefault = 0;
export const listAuditLogsQueryOffsetMin = 0;

export const ListAuditLogsQueryParams = zod.object({
  action: zod.coerce
    .string()
    .optional()
    .describe('Exact action name to filter by (e.g. \"group.force_delete\").'),
  forcedOnly: zod.coerce
    .boolean()
    .default(listAuditLogsQueryForcedOnlyDefault)
    .describe(
      "When true, restrict results to forced deletions only — i.e. rows\nwith action `group.force_delete`, `user.force_delete`,\n`app.force_delete`, `service.force_delete`, or any\n`group.delete`\/`user.delete`\/`app.delete` row whose metadata\ncontains `force: true`. Overrides the `action` filter when set.\n",
    ),
  from: zod
    .date()
    .optional()
    .describe("Start date (inclusive, YYYY-MM-DD UTC)."),
  to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
  targetType: zod.coerce
    .string()
    .optional()
    .describe(
      'Restrict results to entries whose `target_type` exactly matches\n(e.g. \"group\", \"app\", \"role\", \"user\", \"service\", \"settings\").\nCombines with `targetId` and the other filters.\n',
    ),
  targetId: zod.coerce
    .number()
    .min(1)
    .optional()
    .describe(
      "Restrict results to entries whose `target_id` exactly matches.\nTypically used together with `targetType` to focus on the history\nof a single entity.\n",
    ),
  limit: zod.coerce
    .number()
    .min(1)
    .max(listAuditLogsQueryLimitMax)
    .default(listAuditLogsQueryLimitDefault),
  offset: zod.coerce
    .number()
    .min(listAuditLogsQueryOffsetMin)
    .default(listAuditLogsQueryOffsetDefault),
});

export const ListAuditLogsResponse = zod.object({
  entries: zod.array(
    zod.object({
      id: zod.number(),
      action: zod.string(),
      targetType: zod.string(),
      targetId: zod.number().nullable(),
      metadata: zod.record(zod.string(), zod.unknown()).nullable(),
      createdAt: zod.coerce.date(),
      actor: zod.union([
        zod.object({
          id: zod.number(),
          username: zod.string(),
          displayNameAr: zod.string().nullish(),
          displayNameEn: zod.string().nullish(),
          avatarUrl: zod.string().nullish(),
        }),
        zod.null(),
      ]),
    }),
  ),
  totalCount: zod.number(),
  limit: zod.number(),
  offset: zod.number(),
  nextOffset: zod.number().nullable(),
  actions: zod
    .array(zod.string())
    .describe(
      "All distinct action names present in the audit log (for building filter UI).",
    ),
});

/**
 * Streams the filtered audit log as a CSV download.
Honors the same `action`, `forcedOnly`, `from`, and `to` filters as the
list endpoint.
Columns: action, actor_username, target_type, target_id, created_at, metadata.
Capped at 50,000 rows per export to bound response size.

 * @summary Export filtered audit log as CSV (admin)
 */
export const exportAuditLogsCsvQueryForcedOnlyDefault = false;

export const ExportAuditLogsCsvQueryParams = zod.object({
  action: zod.coerce
    .string()
    .optional()
    .describe('Exact action name to filter by (e.g. \"group.force_delete\").'),
  forcedOnly: zod.coerce
    .boolean()
    .default(exportAuditLogsCsvQueryForcedOnlyDefault)
    .describe(
      "When true, restrict the export to forced deletions only.\nSee the list endpoint for the exact set of matching rows.\nOverrides the `action` filter when set.\n",
    ),
  from: zod
    .date()
    .optional()
    .describe("Start date (inclusive, YYYY-MM-DD UTC)."),
  to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
  targetType: zod.coerce
    .string()
    .optional()
    .describe(
      "Restrict the export to entries whose `target_type` exactly matches.\nCombines with `targetId` and the other filters.\n",
    ),
  targetId: zod.coerce
    .number()
    .min(1)
    .optional()
    .describe(
      "Restrict the export to entries whose `target_id` exactly matches.\n",
    ),
});
