/**
 * Nominally-typed primitive types.
 *
 * See:
 * - https://basarat.gitbook.io/typescript/main-1/nominaltyping#using-interfaces
 * - https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698
 */
import { Opaque } from "ts-essentials";

export type AscDesc = "asc" | "desc";

export function maybe<A, B>(box: (value: B) => A, value: B | undefined): A | undefined {
  if (value === undefined) return undefined;
  return box(value);
}

export type OriginRequestPath = Opaque<string, "OriginRequestPath">;
export function OriginRequestPath(value: string): OriginRequestPath {
  return value as any;
}

export type OriginRequestTransformationHeaderValue = Opaque<string, "OriginRequestTransformationHeaderValue">;
export function OriginRequestTransformationHeaderValue(value: string): OriginRequestTransformationHeaderValue {
  return value as any;
}

export type TransformationRootPath = Opaque<string, "TransformationRootPath">;
export function TransformationRootPath(value: string): TransformationRootPath {
  return value as any;
}

export type OrganizationName = Opaque<string, "OrganizationName">;
export function OrganizationName(value: string): OrganizationName {
  return value as any;
}

export type DepartmentName = Opaque<string, "DepartmentName">;
export function DepartmentName(value: string): DepartmentName {
  return value as any;
}

export type AccountId = Opaque<string, "AccountId">;
export function AccountId(value: string): AccountId {
  return value as any;
}

export type FileId = Opaque<string, "FileId">;
export function FileId(value: string): FileId {
  return value as any;
}

export type UserId = Opaque<string, "UserId">;
export function UserId(value: string): UserId {
  return value as any;
}

export type FileListShard = Opaque<number, "FileListShard">;
export function FileListShard(value: number): FileListShard {
  return value as any;
}

export type FileListShardCount = Opaque<number, "FileListShardCount">;
export function FileListShardCount(value: number): FileListShardCount {
  return value as any;
}

export type FileEventShard = Opaque<number, "FileEventShard">;
export function FileEventShard(value: number): FileEventShard {
  return value as any;
}

export type FileEventShardCount = Opaque<number, "FileEventShardCount">;
export function FileEventShardCount(value: number): FileEventShardCount {
  return value as any;
}

export type EventType = Opaque<string, "EventType">;
export function EventType(value: string): EventType {
  return value as any;
}

export type UploadPartIndex = Opaque<number, "UploadPartIndex">;
export function UploadPartIndex(value: number): UploadPartIndex {
  return value as any;
}

export type ArtifactPath = Opaque<string, "ArtifactPath">;
export function ArtifactPath(value: string): ArtifactPath {
  return value as any;
}

export type RequestId = Opaque<string, "RequestId">;
export function RequestId(value: string): RequestId {
  return value as any;
}

export type TransformationFunctionId = Opaque<string, "TransformationFunctionId">;
export function TransformationFunctionId(value: string): TransformationFunctionId {
  return value as any;
}

export type TrustZoneId = Opaque<string, "TrustZoneId">;
export function TrustZoneId(value: string): TrustZoneId {
  return value as any;
}

export type LambdaFunctionName = Opaque<string, "LambdaFunctionName">;
export function LambdaFunctionName(value: string): LambdaFunctionName {
  return value as any;
}

export type NpmPackageName = Opaque<string, "NpmPackageName">;
export function NpmPackageName(value: string): NpmPackageName {
  return value as any;
}

export type NpmPackageVersion = Opaque<string, "NpmPackageVersion">;
export function NpmPackageVersion(value: string): NpmPackageVersion {
  return value as any;
}

export type NpmUsername = Opaque<string, "NpmUsername">;
export function NpmUsername(value: string): NpmUsername {
  return value as any;
}

export type CacheKey = Opaque<string, "CacheKey">;
export function CacheKey(value: string): CacheKey {
  return value as any;
}

export type Stage = Opaque<string, "Stage">;
export function Stage(value: string): Stage {
  return value as any;
}

export type RoleArn = Opaque<string, "RoleArn">;
export function RoleArn(value: string): RoleArn {
  return value as any;
}

export type DynamoDBTableName = Opaque<string, "DynamoDBTableName">;
export function DynamoDBTableName(value: string): DynamoDBTableName {
  return value as any;
}

export type KinesisShardId = Opaque<string, "KinesisShardId">;
export function KinesisShardId(value: string): KinesisShardId {
  return value as any;
}

export type KinesisPartitionKey = Opaque<string, "KinesisPartitionKey">;
export function KinesisPartitionKey(value: string): KinesisPartitionKey {
  return value as any;
}

export type KinesisStreamName = Opaque<string, "KinesisStreamName">;
export function KinesisStreamName(value: string): KinesisStreamName {
  return value as any;
}

export type AwsRegion = Opaque<string, "AwsRegion">;
export function AwsRegion(value: string): AwsRegion {
  return value as any;
}

export type AwsAccessKey = Opaque<string, "AwsAccessKey">;
export function AwsAccessKey(value: string): AwsAccessKey {
  return value as any;
}

export type AwsSecretKey = Opaque<string, "AwsSecretKey">;
export function AwsSecretKey(value: string): AwsSecretKey {
  return value as any;
}

export type S3BucketName = Opaque<string, "S3BucketName">;
export function S3BucketName(value: string): S3BucketName {
  return value as any;
}

export type CognitoUserPoolId = Opaque<string, "CognitoUserPoolId">;
export function CognitoUserPoolId(value: string): CognitoUserPoolId {
  return value as any;
}

export type CognitoUserPoolClientId = Opaque<string, "CognitoUserPoolClientId">;
export function CognitoUserPoolClientId(value: string): CognitoUserPoolClientId {
  return value as any;
}

// Event IDs are ULIDs (i.e. time-ordered)
export type EventId = Opaque<string, "EventId">;
export function EventId(value: string): EventId {
  return value as any;
}

/**
 * Reflects the order in which an event was published onto a stream. Any two events can be causally ordered based on
 * this number (unlike the event ID, which is granular only down to millisecond-level, and may drift between servers).
 * We store as 'string' instead of 'BigInt' since the latter causes serialization issues in various libraries (e.g. Pino
 * and possibly DynamoDB)... **but for comparison between serial numbers you should first convert to BigInt**
 */
export type EventSequenceNumber = Opaque<string, "EventSequenceNumber">;
export function EventSequenceNumber(value: string): EventSequenceNumber {
  return value as any;
}

export type UploadedFileName = Opaque<string, "UploadedFileName">;
export function UploadedFileName(value: string): UploadedFileName {
  return value as any;
}

export type S3ObjectKey = Opaque<string, "S3ObjectKey">;
export function S3ObjectKey(value: string): S3ObjectKey {
  return value as any;
}

export type S3MultipartUploadId = Opaque<string, "S3MultipartUploadId">;
export function S3MultipartUploadId(value: string): S3MultipartUploadId {
  return value as any;
}

/**
 * ETags are always wrapped in quotes (https://tools.ietf.org/html/rfc2616#section-14.19). However, we don't enforce
 * this and we don't inspect this. In this codebase, ETags are always given to us by S3: we don't try to remove these
 * quotes to save 4 characters of storage per file (since in JSON this serializes to "\"xyz\"") because unwrapping and
 * then re-wrapping again (for the call back to S3) creates a larger surface area for bugs: we simply tunnel what S3
 * provides to the client, to our API, then to S3 again from the API's side, as a blackbox parameter.
 */
export type ETag = Opaque<string, "ETag">;
export function ETag(value: string): ETag {
  return value as any;
}

export type Url = Opaque<string, "Url">;
export function Url(value: string): Url {
  return value as any;
}

export type EmailAddress = Opaque<string, "EmailAddress">;
export function EmailAddress(value: string): EmailAddress {
  return value as any;
}

export type FirstName = Opaque<string, "FirstName">;
export function FirstName(value: string): FirstName {
  return value as any;
}

export type LastName = Opaque<string, "LastName">;
export function LastName(value: string): LastName {
  return value as any;
}

export type FilePath = Opaque<string, "FilePath">;
export function FilePath(value: string): FilePath {
  return value as any;
}

export type Seconds = Opaque<number, "Seconds">;
export function Seconds(value: number): Seconds {
  return value as any;
}

export type Milliseconds = Opaque<number, "Milliseconds">;
export function Milliseconds(value: number): Milliseconds {
  return value as any;
}

export type DistributedLockId = Opaque<string, "DistributedLockId">;
export function DistributedLockId(value: string): DistributedLockId {
  return value as any;
}

export type DistributedLockOwner = Opaque<string, "DistributedLockOwner">;
export function DistributedLockOwner(value: string): DistributedLockOwner {
  return value as any;
}

export type TransformationDefinitionId = Opaque<string, "TransformationDefinitionId">;
export function TransformationDefinitionId(value: string): TransformationDefinitionId {
  return value as any;
}

export type TransformationDescription = Opaque<string, "TransformationDescription">;
export function TransformationDescription(value: string): TransformationDescription {
  return value as any;
}

export type TransformationUrlSlug = Opaque<string, "TransformationUrlSlug">;
export function TransformationUrlSlug(value: string): TransformationUrlSlug {
  return value as any;
}

export type TransformationMimeScope = Opaque<string, "TransformationMimeScope">;
export function TransformationMimeScope(value: string): TransformationMimeScope {
  return value as any;
}

export type TransformationTagScope = Opaque<string, "TransformationTagScope">;
export function TransformationTagScope(value: string): TransformationTagScope {
  return value as any;
}

export type MimeType = Opaque<string, "MimeType">;
export function MimeType(value: string): MimeType {
  return value as any;
}

export type FileTag = Opaque<string, "FileTag">;
export function FileTag(value: string): FileTag {
  return value as any;
}

// Supports up to 8 petabytes
export type SizeInBytes = Opaque<number, "SizeInBytes">;
export function SizeInBytes(value: number): SizeInBytes {
  return value as any;
}

/**
 * An 'object author' is the name of the application that created/updated the object.
 */
export type ApplicationName = Opaque<string, "ApplicationName">;
export function ApplicationName(value: string): ApplicationName {
  return value as any;
}

/**
 * An 'object author version' is the version the author application was at (see above) when it performed the create/update.
 */
export type ApplicationVersion = SemanticVersion;

export type SemanticVersion = Opaque<string, "SemanticVersion">;
export function SemanticVersion(value: string): SemanticVersion {
  return value as any;
}

export type ObjectVersion = Opaque<number, "ObjectVersion">;
export function ObjectVersion(value: number): ObjectVersion {
  return value as any;
}
export function ObjectVersionInc(value: ObjectVersion): ObjectVersion {
  return ObjectVersion(value.valueOf() + 1);
}

export type EpochMillis = Opaque<number, "EpochMillis">;
export function EpochMillis(value: number): EpochMillis {
  return value as any;
}

export type EpochSeconds = Opaque<number, "EpochSeconds">;
export function EpochSeconds(value: number): EpochSeconds {
  return value as any;
}

export type PageSize = Opaque<number, "PageSize">;
export function PageSize(value: number): PageSize {
  return value as any;
}

export type ContinuationToken = Opaque<string, "ContinuationToken">;
export function ContinuationToken(value: string): ContinuationToken {
  return value as any;
}

/**
 * This is typically a user ID in the user's system. We just store whatever they provide: it's for the user's
 * organisation of their files only. The uploader ID does not correspond to a user ID in our system.
 */
export type CustomUserId = Opaque<string, "CustomUserId">;
export function CustomUserId(value: string): CustomUserId {
  return value as any;
}

export type ApiKeyDescription = Opaque<string, "ApiKeyDescription">;
export function ApiKeyDescription(value: string): ApiKeyDescription {
  return value as any;
}

export type PublicApiKey = Opaque<string, "PublicApiKey">;
export function PublicApiKey(value: string): PublicApiKey {
  return value as any;
}

export type SecretApiKey = Opaque<string, "SecretApiKey">;
export function SecretApiKey(value: string): SecretApiKey {
  return value as any;
}

// Can be "localhost" too.
export type DomainName = Opaque<string, "DomainName">;
export function DomainName(value: string): DomainName {
  return value as any;
}

export type PluginDescription = Opaque<string, "PluginDescription">;
export function PluginDescription(value: string): PluginDescription {
  return value as any;
}

export type TemplateCategory = Opaque<string, "TemplateCategory">;
export function TemplateCategory(value: string): TemplateCategory {
  return value as any;
}

export type TemplateDescription = Opaque<string, "TemplateDescription">;
export function TemplateDescription(value: string): TemplateDescription {
  return value as any;
}

export type TemplateParamDefault = Opaque<string, "TemplateParamDefault">;
export function TemplateParamDefault(value: string): TemplateParamDefault {
  return value as any;
}

export type TemplateParamDescription = Opaque<string, "TemplateParamDescription">;
export function TemplateParamDescription(value: string): TemplateParamDescription {
  return value as any;
}

export type TemplateParamLabel = Opaque<string, "TemplateParamLabel">;
export function TemplateParamLabel(value: string): TemplateParamLabel {
  return value as any;
}

export type TemplateParamPlaceholder = Opaque<string, "TemplateParamPlaceholder">;
export function TemplateParamPlaceholder(value: string): TemplateParamPlaceholder {
  return value as any;
}
