// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { MainAreaWidget, setToolbar } from '@jupyterlab/apputils';
import { CodeEditor } from '@jupyterlab/codeeditor';
import { Mode } from '@jupyterlab/codemirror';
import { PathExt } from '@jupyterlab/coreutils';
import * as models from '@jupyter/ydoc';
import { nullTranslator } from '@jupyterlab/translation';
import { Signal } from '@lumino/signaling';
/**
 * The default implementation of a document model.
 */
export class DocumentModel extends CodeEditor.Model {
    /**
     * Construct a new document model.
     */
    constructor(languagePreference, modelDB, collaborationEnabled) {
        super({ modelDB });
        this._defaultLang = '';
        this._dirty = false;
        this._readOnly = false;
        this._contentChanged = new Signal(this);
        this._stateChanged = new Signal(this);
        this._defaultLang = languagePreference || '';
        const filemodel = new models.YFile();
        this.switchSharedModel(filemodel, true);
        this.value.changed.connect(this.triggerContentChange, this);
        this.sharedModel.changed.connect(this._onStateChanged, this);
        this._collaborationEnabled = !!collaborationEnabled;
    }
    /**
     * A signal emitted when the document content changes.
     */
    get contentChanged() {
        return this._contentChanged;
    }
    /**
     * A signal emitted when the document state changes.
     */
    get stateChanged() {
        return this._stateChanged;
    }
    /**
     * The dirty state of the document.
     */
    get dirty() {
        return this._dirty;
    }
    set dirty(newValue) {
        const oldValue = this._dirty;
        if (newValue === oldValue) {
            return;
        }
        this._dirty = newValue;
        this.triggerStateChange({
            name: 'dirty',
            oldValue,
            newValue
        });
    }
    /**
     * The read only state of the document.
     */
    get readOnly() {
        return this._readOnly;
    }
    set readOnly(newValue) {
        if (newValue === this._readOnly) {
            return;
        }
        const oldValue = this._readOnly;
        this._readOnly = newValue;
        this.triggerStateChange({ name: 'readOnly', oldValue, newValue });
    }
    /**
     * The default kernel name of the document.
     *
     * #### Notes
     * This is a read-only property.
     */
    get defaultKernelName() {
        return '';
    }
    /**
     * The default kernel language of the document.
     *
     * #### Notes
     * This is a read-only property.
     */
    get defaultKernelLanguage() {
        return this._defaultLang;
    }
    /**
     * Whether the model is collaborative or not.
     */
    get collaborative() {
        return this._collaborationEnabled;
    }
    /**
     * Serialize the model to a string.
     */
    toString() {
        return this.value.text;
    }
    /**
     * Deserialize the model from a string.
     *
     * #### Notes
     * Should emit a [contentChanged] signal.
     */
    fromString(value) {
        this.value.text = value;
    }
    /**
     * Serialize the model to JSON.
     */
    toJSON() {
        return JSON.parse(this.value.text || 'null');
    }
    /**
     * Deserialize the model from JSON.
     *
     * #### Notes
     * Should emit a [contentChanged] signal.
     */
    fromJSON(value) {
        this.fromString(JSON.stringify(value));
    }
    /**
     * Initialize the model with its current state.
     */
    initialize() {
        return;
    }
    /**
     * Trigger a state change signal.
     */
    triggerStateChange(args) {
        this._stateChanged.emit(args);
    }
    /**
     * Trigger a content changed signal.
     */
    triggerContentChange() {
        this._contentChanged.emit(void 0);
        this.dirty = true;
    }
    _onStateChanged(sender, changes) {
        if (changes.sourceChange) {
            this.triggerContentChange();
        }
        if (changes.stateChange) {
            changes.stateChange.forEach(value => {
                if (value.name === 'dirty') {
                    // Setting `dirty` will trigger the state change.
                    // We always set `dirty` because the shared model state
                    // and the local attribute are synchronized one way shared model -> _dirty
                    this.dirty = value.newValue;
                }
                else if (value.oldValue !== value.newValue) {
                    this.triggerStateChange(Object.assign({ newValue: undefined, oldValue: undefined }, value));
                }
            });
        }
    }
}
/**
 * An implementation of a model factory for text files.
 */
export class TextModelFactory {
    /**
     * Instantiates a TextModelFactory.
     */
    constructor(collaborative) {
        this._isDisposed = false;
        this._collaborative = collaborative !== null && collaborative !== void 0 ? collaborative : true;
    }
    /**
     * The name of the model type.
     *
     * #### Notes
     * This is a read-only property.
     */
    get name() {
        return 'text';
    }
    /**
     * The type of the file.
     *
     * #### Notes
     * This is a read-only property.
     */
    get contentType() {
        return 'file';
    }
    /**
     * The format of the file.
     *
     * This is a read-only property.
     */
    get fileFormat() {
        return 'text';
    }
    /**
     * Whether the model is collaborative or not.
     */
    get collaborative() {
        return this._collaborative;
    }
    /**
     * Get whether the model factory has been disposed.
     */
    get isDisposed() {
        return this._isDisposed;
    }
    /**
     * Dispose of the resources held by the model factory.
     */
    dispose() {
        this._isDisposed = true;
    }
    /**
     * Create a new model.
     *
     * @param languagePreference - An optional kernel language preference.
     * @param modelDB - An optional model storage.
     * @param isInitialized - Whether the model is initialized or not.
     * @param collaborationEnabled - Whether collaboration is enabled at the application level or not (default `false`).
     *
     * @returns A new document model.
     */
    createNew(languagePreference, modelDB, isInitialized, collaborationEnabled) {
        const collaborative = collaborationEnabled && this.collaborative;
        return new DocumentModel(languagePreference, modelDB, collaborative);
    }
    /**
     * Get the preferred kernel language given a file path.
     */
    preferredLanguage(path) {
        const mode = Mode.findByFileName(path);
        return mode && mode.mode;
    }
}
/**
 * An implementation of a model factory for base64 files.
 */
export class Base64ModelFactory extends TextModelFactory {
    /**
     * The name of the model type.
     *
     * #### Notes
     * This is a read-only property.
     */
    get name() {
        return 'base64';
    }
    /**
     * The type of the file.
     *
     * #### Notes
     * This is a read-only property.
     */
    get contentType() {
        return 'file';
    }
    /**
     * The format of the file.
     *
     * This is a read-only property.
     */
    get fileFormat() {
        return 'base64';
    }
}
/**
 * The default implementation of a widget factory.
 */
export class ABCWidgetFactory {
    /**
     * Construct a new `ABCWidgetFactory`.
     */
    constructor(options) {
        this._isDisposed = false;
        this._widgetCreated = new Signal(this);
        this._translator = options.translator || nullTranslator;
        this._name = options.name;
        this._readOnly = options.readOnly === undefined ? false : options.readOnly;
        this._defaultFor = options.defaultFor ? options.defaultFor.slice() : [];
        this._defaultRendered = (options.defaultRendered || []).slice();
        this._fileTypes = options.fileTypes.slice();
        this._modelName = options.modelName || 'text';
        this._preferKernel = !!options.preferKernel;
        this._canStartKernel = !!options.canStartKernel;
        this._shutdownOnClose = !!options.shutdownOnClose;
        this._toolbarFactory = options.toolbarFactory;
    }
    /**
     * A signal emitted when a widget is created.
     */
    get widgetCreated() {
        return this._widgetCreated;
    }
    /**
     * Get whether the model factory has been disposed.
     */
    get isDisposed() {
        return this._isDisposed;
    }
    /**
     * Dispose of the resources used by the document manager.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        this._isDisposed = true;
        Signal.clearData(this);
    }
    /**
     * Whether the widget factory is read only.
     */
    get readOnly() {
        return this._readOnly;
    }
    /**
     * The name of the widget to display in dialogs.
     */
    get name() {
        return this._name;
    }
    /**
     * The file types the widget can view.
     */
    get fileTypes() {
        return this._fileTypes.slice();
    }
    /**
     * The registered name of the model type used to create the widgets.
     */
    get modelName() {
        return this._modelName;
    }
    /**
     * The file types for which the factory should be the default.
     */
    get defaultFor() {
        return this._defaultFor.slice();
    }
    /**
     * The file types for which the factory should be the default for
     * rendering a document model, if different from editing.
     */
    get defaultRendered() {
        return this._defaultRendered.slice();
    }
    /**
     * Whether the widgets prefer having a kernel started.
     */
    get preferKernel() {
        return this._preferKernel;
    }
    /**
     * Whether the widgets can start a kernel when opened.
     */
    get canStartKernel() {
        return this._canStartKernel;
    }
    /**
     * The application language translator.
     */
    get translator() {
        return this._translator;
    }
    /**
     * Whether the kernel should be shutdown when the widget is closed.
     */
    get shutdownOnClose() {
        return this._shutdownOnClose;
    }
    set shutdownOnClose(value) {
        this._shutdownOnClose = value;
    }
    /**
     * Create a new widget given a document model and a context.
     *
     * #### Notes
     * It should emit the [widgetCreated] signal with the new widget.
     */
    createNew(context, source) {
        var _a;
        // Create the new widget
        const widget = this.createNewWidget(context, source);
        // Add toolbar
        setToolbar(widget, (_a = this._toolbarFactory) !== null && _a !== void 0 ? _a : this.defaultToolbarFactory.bind(this));
        // Emit widget created signal
        this._widgetCreated.emit(widget);
        return widget;
    }
    /**
     * Default factory for toolbar items to be added after the widget is created.
     */
    defaultToolbarFactory(widget) {
        return [];
    }
}
/**
 * The class name added to a dirty widget.
 */
const DIRTY_CLASS = 'jp-mod-dirty';
/**
 * A document widget implementation.
 */
export class DocumentWidget extends MainAreaWidget {
    constructor(options) {
        // Include the context ready promise in the widget reveal promise
        options.reveal = Promise.all([options.reveal, options.context.ready]);
        super(options);
        this.context = options.context;
        // Handle context path changes
        this.context.pathChanged.connect(this._onPathChanged, this);
        this._onPathChanged(this.context, this.context.path);
        // Listen for changes in the dirty state.
        this.context.model.stateChanged.connect(this._onModelStateChanged, this);
        void this.context.ready.then(() => {
            this._handleDirtyState();
        });
        // listen for changes to the title object
        this.title.changed.connect(this._onTitleChanged, this);
    }
    /**
     * Set URI fragment identifier.
     */
    setFragment(fragment) {
        /* no-op */
    }
    /**
     * Handle a title change.
     */
    async _onTitleChanged(_sender) {
        const validNameExp = /[\/\\:]/;
        const name = this.title.label;
        const filename = this.context.path.split('/').pop();
        if (name === filename) {
            return;
        }
        if (name.length > 0 && !validNameExp.test(name)) {
            const oldPath = this.context.path;
            await this.context.rename(name);
            if (this.context.path !== oldPath) {
                // Rename succeeded
                return;
            }
        }
        // Reset title if name is invalid or rename fails
        this.title.label = filename;
    }
    /**
     * Handle a path change.
     */
    _onPathChanged(sender, path) {
        this.title.label = PathExt.basename(sender.localPath);
        // The document is not untitled any more.
        this.isUntitled = false;
    }
    /**
     * Handle a change to the context model state.
     */
    _onModelStateChanged(sender, args) {
        if (args.name === 'dirty') {
            this._handleDirtyState();
        }
    }
    /**
     * Handle the dirty state of the context model.
     */
    _handleDirtyState() {
        if (this.context.model.dirty &&
            !this.title.className.includes(DIRTY_CLASS)) {
            this.title.className += ` ${DIRTY_CLASS}`;
        }
        else {
            this.title.className = this.title.className.replace(DIRTY_CLASS, '');
        }
    }
}
//# sourceMappingURL=default.js.map