import { Failure } from '../../fail';
import { DeepImmutable, Observable } from '../babylonjs-import';
import { BimChange, BimChangeLayer, BimContainer, BimFolder, BlobData } from '../bim-api-client';

/**
 * Defines change types that it is possible to upload files/content for using the {@link UploadApiClient} api.
 * Currenty it is only supported to upload files that aren't IFC or DWG's.
 */
export type BimChangeUploadable = Exclude<BimChange, BimChangeLayer | BimFolder | BimContainer>;

/**
 * The default Metadata for a change (file/attachment) that is about to be uploaded.
 */
export interface DefaultChangeUploadableMetadata {
    /** Type of metadata */
    contentTypeId: 'default';
    /** Description */
    description: string;
    /**
     * Custom data. Can be any JSON structure. Do not store large data structures
     * as it will be stored in the database. It is possible to create {@link BimChangeMetadataQuery} queries
     * for this data.
     * * @example
     * ```typescript
     * // customData was set to '{ "photo": { "type": "360", "height": 1024, "width": 2048 } }' when change was uploaded.
     * const changes = await api.getChanges(container, { query: "customData.photo.type=360" });
     * ```
     * @remarks
     * It is intentionally not possible assign a array directly to {@link customData}. It is possible to
     * assign arrays to a nested properties though.
     */
    customData?: Record<string, object | string | number | boolean>;
}

/**
 * Defines what is required to create a new change (file) in a existing folder or layer..
 */
export interface UploadClientOperationCreate {
    /**
     * Type of operation. In this case 'create' to represent file creation.
     */
    operation: 'create';

    /**
     * Folder or layer to create change (file) in
     */
    parent: BimFolder | BimChangeLayer;

    /**
     * Filename that the change will get.
     */
    filename: string;

    /**
     * Content of the file
     */
    content: BlobData;

    /**
     * Metadata.
     */
    metadata: DefaultChangeUploadableMetadata;
}

/**
 * Defines what is required to create a new change (file) in a existing folder.
 * @deprecated
 * Use {@link UploadClientOperationCreate} instead. It supports both folders and layers.
 */
export interface UploadClientOperationCreateFile extends Omit<UploadClientOperationCreate, 'parent'> {
    /**
     * Folder to create change (file) in
     */
    folder: BimFolder;
}

/**
 * Defines what is required to append a version to a existing change. This can be a existing change like
 * a file or a attachment.
 */
export interface UploadClientOperationAppendFileVersion<Change extends BimChangeUploadable> {
    /**
     * Type of operation. In this case 'append' to represent that the operation will add  new
     * version to a existing change.
     */
    operation: 'append';

    /**
     * Previous change to add version to. If {@link Change.etag} on {@link previousVersion} differs from backend value
     * then the operation will fail.
     */
    previousVersion: Change;

    /**
     * Content to add.
     */
    content: BlobData;

    /**
     * Optional metadata. If metadata is not specified then metadata from {@link previousVersion} is automatically used.
     * If specified, then all metadata must be specified because it will be used instead of metadata
     * from {@link previousVersion}.
     */
    metadata?: DefaultChangeUploadableMetadata;
}
/**
 * Defines available operations for the {@link UploadApiClient.createFileSession} method.
 */
export type UploadApiClientContentOperation<Change extends BimChangeUploadable> =
    | UploadClientOperationAppendFileVersion<Change>
    | UploadClientOperationCreate
    | UploadClientOperationCreateFile;

/**
 * Defines options for the {@link UploadApiClient.createFileSession} method.
 */
export interface UploadApiClientContentOptions {
    /**
     * Optional chunksize. Must be > 0 and a integer. When a content is uploaded
     * it is sliced into chunks of this size before being uploaded.
     */
    chunkSize?: number;
}

/**
 * Represents the failure type.
 */
export type UploadApiClientFailure = Failure<{
    /**
     * HTTP status code.
     */
    status: number;
    /**
     * Gives reason for failure
     */
    reason: string;
}>;

/**
 * Represents content that is about to be uploaded.
 */
export interface UploadApiTask<Change extends BimChangeUploadable> {
    /**
     * Triggered every time a chunk has been sucessfully uploaded.
     */
    readonly onChunkuploaded: Observable<UploadApiTask<Change>>;

    /**
     * Size in bytes of the content that is to be uploaded.
     */
    readonly sizeInBytes: number;

    /**
     * Number of bytes that have currently been uploaded
     */
    readonly bytesUploaded: number;

    /**
     * Progress of upload in range of [0, 100].
     */
    readonly progress: number;

    /**
     * cancels the ongoing upload.
     * @returns boolean promise which evaluates to `true` if cancellation was a success.
     */
    cancel(): Promise<boolean>;

    /**
     * Starts uploading the content.
     * @returns either a change, representing the new version that the upload resulted in, or {@link UploadApiClientFailure}
     * that represents an error.
     */
    upload(): Promise<Change | UploadApiClientFailure>;
}

/**
 * API for uploading content.
 */
export interface UploadApiClient {
    /**
     * Check if it is possible to create a new change (file) in a folder.
     * @param permissions  Folder permissions. See {@link BimChangeUploadable.permissions}.
     * @deprecated Use {@link canCreateFileIn} instead.
     * @returns `true` if it is possible to upload content into the folder and create a new file (no previous version may exist.)
     */
    canCreateFileInFolder({ permissions }: DeepImmutable<BimFolder>): boolean;

    /**
     * Check if it is possible to create a new change (file) in a folder or a file.
     * @param permissions  Folder permissions. See {@link BimChangeUploadable.permissions}.
     * @returns `true` if it is possible to upload content into the folder and create a new file (no previous version may exist.)
     */
    canCreateFileIn({ permissions }: DeepImmutable<BimFolder | BimChangeLayer>): boolean;

    /**
     * Checks if it is possible to append a version to a existing change.
     * @param permissionsAndOwnerSystem Permissions and owner system of the change to append a version to. See {@link BimChangeUploadable.permissions} and {@link BimChangeUploadable.ownerSystem}.
     * @returns `true` if it is possible to upload content and thereby appending a new version (previous version must exist.)
     */
    canAppendFileVersion({ permissions, ownerSystem }: DeepImmutable<BimChangeUploadable>): boolean;

    /**
     * Creates an upload session (represented by an {@link UploadApiTask})
     * @param operation Defines whehter the upload should create a new change in a folder
     * (previous change may not exist) or if we want to append a version to a existing change
     * @param o Options.
     * @returns A promise of either {@link UploadApiTask}, representing the content that is to be uploaded, or {@link UploadApiClientFailure}
     * representing a error that occured.
     */
    createFileSession<Change extends BimChangeUploadable>(
        operation: UploadApiClientContentOperation<Change>,
        o?: DeepImmutable<UploadApiClientContentOptions>
    ): Promise<UploadApiClientFailure | UploadApiTask<Change>>;
}
