var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { Base64 } from '../util/Base64';
import { Attachment } from './Attachment';
import { ErrorMessage } from './ErrorMessage';
import { PropertyFormatter } from './PropertyFormatter';
import { DialogModeEnum, TypeNames, ViewModeEnum } from './types';
/**
 * Top-level class, representing a Catavolt 'Dialog' definition.
 * All Dialogs have a composite {@link View} definition along with a single record
 * or a list of records.  See {@Record}
 */
export class Dialog {
    constructor() {
        this.children = [];
        // private/protected
        this._lastRefreshTime = new Date(0);
    }
    // protected _parentDialog;
    /* public methods */
    static isSearchDialog(dialog) {
        return dialog.dialogClassName && dialog.dialogClassName.indexOf(Dialog.SEARCH_DIALOG_CLASS) > -1 &&
            dialog.view && dialog.view.type === TypeNames.DetailsTypeName;
    }
    get catavolt() {
        return this._catavolt;
    }
    get anyChildNeedsRefresh() {
        return (this.children &&
            this.children.some((dialog) => {
                return dialog.isRefreshNeeded;
            }));
    }
    destroy() {
        return this.catavolt.dialogApi.deleteDialog(this.tenantId, this.sessionId, this.id).then(() => {
            this.dialogMode = DialogModeEnum.DESTROYED;
        });
    }
    /**
     * Return the error associated with this dialog, if any
     * @returns {}
     */
    get error() {
        if (this.hasError) {
            return this.view.exception;
        }
        else {
            return null;
        }
    }
    /**
     * Find a menu def on this dialog with the given actionId
     * @param actionId
     * @returns {Menu}
     */
    findMenuAt(actionId) {
        return this.view.findMenuAt(actionId);
    }
    /**
     * Get a string representation of this property suitable for 'reading'
     *
     * @param {Property} prop
     * @param {string} propName
     * @returns {string}
     */
    formatForRead(prop, propName) {
        return PropertyFormatter.singleton(this._catavolt).formatForRead(prop, this.propDefAtName(propName));
    }
    /**
     * Get a string representation of this property suitable for 'writing'
     *
     * @param {Property} prop
     * @param {string} propName
     * @returns {string}
     */
    formatForWrite(prop, propName) {
        return PropertyFormatter.singleton(this.catavolt).formatForWrite(prop, this.propDefAtName(propName));
    }
    /**
     * Returns whether or not this dialog loaded properly
     * @returns {boolean}
     */
    get hasError() {
        return this.view instanceof ErrorMessage;
    }
    /**
     * Returns whether or not this Form is destroyed
     * @returns {boolean}
     */
    get isDestroyed() {
        return this.dialogMode === DialogModeEnum.DESTROYED || this.isAnyChildDestroyed;
    }
    /**
     * Returns whether or not the data in this dialog is out of date
     * @returns {boolean}
     */
    get isRefreshNeeded() {
        return this._lastRefreshTime.getTime() < this.catavolt.dataLastChangedTime.getTime();
    }
    get isReadViewMode() {
        return this.viewMode === ViewModeEnum.READ;
    }
    /**
     * Get the last time this dialog's data was refreshed
     * @returns {Date}
     */
    get lastRefreshTime() {
        return this._lastRefreshTime;
    }
    /**
     * @param time
     */
    set lastRefreshTime(time) {
        this._lastRefreshTime = time;
    }
    /**
     * Get the all {@link Menu}'s associated with this dialog
     * @returns {Array<Menu>}
     */
    get menu() {
        return this.view.menu;
    }
    openViewWithId(viewId) {
        return this.catavolt.dialogApi
            .changeView(this.tenantId, this.sessionId, this.id, viewId)
            .then((dialog) => {
            // any new dialog needs to be initialized with the Catavolt object
            dialog.initialize(this.catavolt);
            this.updateSettingsWithNewDialogProperties(dialog.referringObject);
            return dialog;
        });
    }
    openView(targetViewDescriptor) {
        return this.openViewWithId(targetViewDescriptor.id);
    }
    /**
     * Get the title of this dialog
     * @returns {string}
     */
    get paneTitle() {
        let title = this.view.findTitle();
        if (!title) {
            title = this.description;
        }
        return title;
    }
    /**
     * Parses a value to prepare for 'writing' back to the server
     * @param formattedValue
     * @param propName
     * @returns {}
     */
    parseValue(formattedValue, propName) {
        return PropertyFormatter.singleton(this._catavolt).parse(formattedValue, this.propDefAtName(propName));
    }
    /**
     * Get the property definition for a property name
     * @param propName
     * @returns {PropertyDef}
     */
    propDefAtName(propName) {
        return this.recordDef.propDefAtName(propName);
    }
    /**
     * Read all the large property values into memory in this {@link Record}
     *
     * @param {string} recordId
     * @returns {Promise<LargeProperty[]>}
     */
    readLargeProperties(recordId) {
        return Promise.all(this.recordDef.propertyDefs
            .filter((propDef) => {
            return propDef.isLargePropertyType;
        })
            .map((propDef) => {
            return this.readLargeProperty(propDef.propertyName, recordId);
        }));
    }
    /**
     * Read a large property into memory
     *
     * @param {string} propertyName
     * @param {string} recordId
     * @returns {Promise<LargeProperty>}
     */
    readLargeProperty(propertyName, recordId) {
        return this.loadLargeProperty(propertyName, recordId);
    }
    /**
     * Stream the encoded chunks of a large property without retaining them
     * The streamConsumer will receive Base64 encoded chunks with callbacks. hasMore will
     * be false with the final chunk.
     * @param {StreamConsumer} streamConsumer
     * @param {string} propertyName
     * @param {string} recordId
     * @returns {Promise<LargeProperty>}
     */
    streamLargeProperty(streamConsumer, propertyName, recordId) {
        return this.loadLargeProperty(propertyName, recordId, streamConsumer);
    }
    /*
    get parentDialog():Dialog {
        return this._parentDialog;
    }
    */
    /**
     * Get the all {@link ViewDescriptor}'s associated with this Form
     * @returns {Array<ViewDescriptor>}
     */
    get viewDescs() {
        return this.availableViews;
    }
    initialize(catavolt) {
        this._catavolt = catavolt;
        if (this.children) {
            this.children.forEach((child) => {
                // @TODO add this if needed
                // child._parentDialog = this;
                child.initialize(catavolt);
            });
        }
    }
    invokeMenuActionWithId(actionId, actionParams) {
        return this.catavolt.dialogApi
            .performAction(this.tenantId, this.sessionId, this.id, actionId, actionParams)
            .then((result) => {
            // @TODO - update relevant referring dialog settings on 'this' dialog
            this.updateSettingsWithNewDialogProperties(result.referringObject);
            if (result.refreshNeeded) {
                this.catavolt.dataLastChangedTime = new Date();
            }
            return result;
        });
    }
    /**
     * Perform this action associated with the given Menu on this dialog.
     * The targets array is expected to be an array of object ids.
     * @param {Menu} menu
     * @param {ActionParameters} actionParams
     * @returns {Promise<{actionId: string} | Redirection>}
     */
    invokeMenuAction(menu, actionParams) {
        return this.invokeMenuActionWithId(menu.actionId, actionParams);
    }
    updateSettingsWithNewDialogProperties(referringObject) {
        if (referringObject) {
            if (referringObject.isDialogReferrer()) {
                const referringDialog = referringObject;
                if (referringDialog.dialogMode) {
                    // @TODO - remove the uppercase conversion once all DialogModes come back from server as uppercase
                    this.dialogMode = referringDialog.dialogMode.toUpperCase();
                }
            }
        }
    }
    /* @TODO */
    writeAttachment(attachment) {
        /*
         return DialogService.addAttachment(this.dialogRedirection.dialogHandle, attachment, this.session);
         */
        return Promise.resolve(null);
    }
    writeAttachments(record) {
        return Promise.all(record.properties
            .filter((prop) => {
            return prop.value instanceof Attachment;
        })
            .map((prop) => {
            const attachment = prop.value;
            return this.writeAttachment(attachment);
        }));
    }
    /**
     * Write all Binary values in this {@link Record} back to the server
     *
     * @param {Record} record
     * @returns {Promise<void[]>}
     */
    writeLargeProperties(record) {
        return Promise.all(record.properties
            .filter((prop) => {
            return this.propDefAtName(prop.name).isLargePropertyType;
        })
            .map((prop) => {
            return this.writeLargeProperty(prop.name, prop.value);
        }));
    }
    writeLargeProperty(propertyName, largeProperty) {
        // This is a delete
        if (!largeProperty || !largeProperty.encodedData) {
            return this.catavolt.dialogApi
                .writeProperty(this.tenantId, this.sessionId, this.id, propertyName, {
                append: false,
                encodedData: null,
                type: TypeNames.WriteLargePropertyParameters
            })
                .then(() => Promise.resolve());
        }
        const data = Base64.decodeString(largeProperty.encodedData);
        const f = (ptr) => {
            if (ptr < data.length) {
                const segment = ptr + Dialog.CHAR_CHUNK_SIZE <= data.length
                    ? data.substr(ptr, Dialog.CHAR_CHUNK_SIZE)
                    : data.substring(ptr);
                const params = {
                    append: ptr !== 0,
                    encodedData: Base64.encodeString(segment),
                    type: TypeNames.WriteLargePropertyParameters
                };
                return this.catavolt.dialogApi
                    .writeProperty(this.tenantId, this.sessionId, this.id, propertyName, params)
                    .then(() => {
                    return f(ptr + Dialog.CHAR_CHUNK_SIZE);
                });
            }
            else {
                return Promise.resolve();
            }
        };
        return f(0);
    }
    /**
     * @private
     * @returns {boolean}
     */
    get isAnyChildDestroyed() {
        return (this.children &&
            this.children.some((dialog) => {
                return dialog.isDestroyed;
            }));
    }
    /**
     * Read a large property into memory or stream it, if a streamConsumer is provided
     * @param {string} propertyName
     * @param {string} recordId
     * @param {StreamConsumer} streamConsumer
     * @returns {Promise<LargeProperty>}
     */
    loadLargeProperty(propertyName, recordId, streamConsumer) {
        return Dialog.loadLargeProperty(this.getProperty.bind(this), streamConsumer, propertyName, recordId);
    }
    /**
     * Read a large property into memory or stream it, if a streamConsumer is provided
     * The actual service call that retrieves the result is delegate to the 'getPropertyFn'
     * @param {(params: ReadLargePropertyParameters, propertyName?: string) => Promise<LargeProperty>} getPropertyFn
     * @param {StreamConsumer} streamConsumer
     * @param {string} propertyName
     * @param {string} recordId
     * @returns {Promise<LargeProperty>}
     */
    static loadLargeProperty(getPropertyFn, streamConsumer, propertyName, recordId) {
        let sequence = 0;
        let resultBuffer = '';
        const f = (largeProperty) => __awaiter(this, void 0, void 0, function* () {
            streamConsumer &&
                (yield streamConsumer({ done: !largeProperty.hasMore, value: largeProperty.encodedData }));
            if (largeProperty.hasMore) {
                if (!streamConsumer) {
                    resultBuffer += Base64.decodeString(largeProperty.encodedData);
                }
                const params = {
                    maxBytes: Dialog.BINARY_CHUNK_SIZE,
                    sequence: ++sequence,
                    recordId,
                    type: TypeNames.ReadLargePropertyParameters
                };
                return getPropertyFn(params, propertyName).then(f);
            }
            else {
                if (resultBuffer) {
                    resultBuffer += Base64.decodeString(largeProperty.encodedData);
                    return Promise.resolve(largeProperty.asNewLargeProperty(Base64.encodeString(resultBuffer)));
                }
                else {
                    if (streamConsumer) {
                        return Promise.resolve(largeProperty.asNewLargeProperty(null));
                    }
                    return Promise.resolve(largeProperty.asNewLargeProperty(largeProperty.encodedData));
                }
            }
        });
        const initParams = {
            maxBytes: Dialog.BINARY_CHUNK_SIZE,
            sequence,
            recordId,
            type: TypeNames.ReadLargePropertyParameters
        };
        return getPropertyFn(initParams, propertyName).then(f);
    }
}
// statics
Dialog.SEARCH_DIALOG_CLASS = 'SearchQueryModel';
Dialog.BINARY_CHUNK_SIZE = 128 * 1024; // size in  byes for 'read' operation
Dialog.CHAR_CHUNK_SIZE = 128 * 1000; // size in chars for encoded 'write' operation
