import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { LazyStatefulPromise, raceTimeout } from '../../../../base/common/async.js';
import { BugIndicatingError, onUnexpectedError } from '../../../../base/common/errors.js';
import { ValueWithChangeEvent, Event } from '../../../../base/common/event.js';
import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { parse } from '../../../../base/common/marshalling.js';
import { Schemas } from '../../../../base/common/network.js';
import { deepClone } from '../../../../base/common/objects.js';
import { observableValue } from '../../../../base/common/observableInternal/base.js';
import { derived } from '../../../../base/common/observableInternal/derived.js';
import { autorun } from '../../../../base/common/observableInternal/autorun.js';
import { observableFromValueWithChangeEvent, constObservable, mapObservableArrayCached, recomputeInitiallyAndOnChange, ValueWithChangeEventFromObservable, observableFromEvent } from '../../../../base/common/observableInternal/utils.js';
import { ObservableLazyPromise } from '../../../../base/common/observableInternal/promise.js';
import '../../../../base/common/arrays.js';
import { isDefined, isObject } from '../../../../base/common/types.js';
import { URI } from '../../../../base/common/uri.js';
import { ConstLazyPromise } from '../../../../editor/browser/widget/multiDiffEditor/model.js';
import { MultiDiffEditorViewModel } from '../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorViewModel.js';
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';
import { localizeWithPath } from '../../../../nls.js';
import { ConfirmResult } from '../../../../platform/dialogs/common/dialogs.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { EditorInputCapabilities, DEFAULT_EDITOR_ASSOCIATION } from '../../../common/editor.js';
import { EditorInput } from '../../../common/editor/editorInput.js';
import { MultiDiffEditorIcon } from './icons.contribution.js';
import { MultiDiffEditorItem } from './multiDiffSourceResolverService.js';
import { IMultiDiffSourceResolverService } from './multiDiffSourceResolverService.service.js';
import { RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js';
import { IEditorResolverService } from '../../../services/editor/common/editorResolverService.service.js';
import { ITextFileService } from '../../../services/textfile/common/textfiles.service.js';
var MultiDiffEditorInput_1;
const _moduleId = "vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput";
let MultiDiffEditorInput = class MultiDiffEditorInput extends EditorInput {
    static { MultiDiffEditorInput_1 = this; }
    static fromResourceMultiDiffEditorInput(input, instantiationService) {
        if (!input.multiDiffSource && !input.resources) {
            throw ( (new BugIndicatingError('MultiDiffEditorInput requires either multiDiffSource or resources')));
        }
        const multiDiffSource = input.multiDiffSource ?? ( (URI.parse(
            `multi-diff-editor:${( (( (new Date())).getMilliseconds().toString())) + ( (Math.random().toString()))}`
        )));
        return instantiationService.createInstance(MultiDiffEditorInput_1, multiDiffSource, input.label, input.resources?.map(resource => {
            return (
                 (new MultiDiffEditorItem(resource.original.resource, resource.modified.resource))
            );
        }), input.isTransient ?? false);
    }
    static fromSerialized(data, instantiationService) {
        return instantiationService.createInstance(MultiDiffEditorInput_1, ( (URI.parse(data.multiDiffSourceUri))), data.label, data.resources?.map(resource => ( (new MultiDiffEditorItem(
            resource.originalUri ? ( (URI.parse(resource.originalUri))) : undefined,
            resource.modifiedUri ? ( (URI.parse(resource.modifiedUri))) : undefined
        )))), false);
    }
    static { this.ID = 'workbench.input.multiDiffEditor'; }
    get resource() { return this.multiDiffSource; }
    get capabilities() { return EditorInputCapabilities.Readonly; }
    get typeId() { return MultiDiffEditorInput_1.ID; }
    getName() { return this._name; }
    get editorId() { return DEFAULT_EDITOR_ASSOCIATION.id; }
    getIcon() { return MultiDiffEditorIcon; }
    constructor(multiDiffSource, label, initialResources, isTransient = false, _textModelService, _textResourceConfigurationService, _instantiationService, _multiDiffSourceResolverService, _textFileService) {
        super();
        this.multiDiffSource = multiDiffSource;
        this.label = label;
        this.initialResources = initialResources;
        this.isTransient = isTransient;
        this._textModelService = _textModelService;
        this._textResourceConfigurationService = _textResourceConfigurationService;
        this._instantiationService = _instantiationService;
        this._multiDiffSourceResolverService = _multiDiffSourceResolverService;
        this._textFileService = _textFileService;
        this._name = '';
        this._viewModel = ( (new LazyStatefulPromise(async () => {
            const model = await this._createModel();
            this._register(model);
            const vm = ( (new MultiDiffEditorViewModel(model, this._instantiationService)));
            this._register(vm);
            await raceTimeout(vm.waitForDiffs(), 1000);
            return vm;
        })));
        this._resolvedSource = ( (new ObservableLazyPromise(async () => {
            const source = this.initialResources
                ? { resources: ValueWithChangeEvent.const(this.initialResources) }
                : await this._multiDiffSourceResolverService.resolve(this.multiDiffSource);
            return {
                source,
                resources: source ? observableFromValueWithChangeEvent(this, source.resources) : constObservable([]),
            };
        })));
        this.resources = derived(this, reader => this._resolvedSource.cachedPromiseResult.read(reader)?.data?.resources.read(reader));
        this._isDirtyObservables = mapObservableArrayCached(this, ( (this.resources.map(r => r ?? []))), res => {
            const isModifiedDirty = res.modified ? isUriDirty(this._textFileService, res.modified) : constObservable(false);
            const isOriginalDirty = res.original ? isUriDirty(this._textFileService, res.original) : constObservable(false);
            return derived(reader =>  isModifiedDirty.read(reader) || isOriginalDirty.read(reader));
        }, i => i.getKey());
        this._isDirtyObservable = derived(this, reader => ( (this._isDirtyObservables.read(reader).some(isDirty => isDirty.read(reader)))))
            .keepObserved(this._store);
        this.onDidChangeDirty = Event.fromObservableLight(this._isDirtyObservable);
        this.closeHandler = {
            async confirm() {
                return ConfirmResult.DONT_SAVE;
            },
            showConfirm() {
                return false;
            }
        };
        this._register(autorun((reader) => {
            const resources = this.resources.read(reader);
            const label = this.label ?? ( localizeWithPath(_moduleId, 0, "Multi Diff Editor"));
            if (resources) {
                this._name = label + ( localizeWithPath(_moduleId, 1, " ({0} files)", resources.length));
            }
            else {
                this._name = label;
            }
            this._onDidChangeLabel.fire();
        }));
    }
    serialize() {
        return {
            label: this.label,
            multiDiffSourceUri: ( (this.multiDiffSource.toString())),
            resources: this.initialResources?.map(resource => ({
                originalUri: resource.original?.toString(),
                modifiedUri: resource.modified?.toString(),
            })),
        };
    }
    setLanguageId(languageId, source) {
        const activeDiffItem = this._viewModel.requireValue().activeDiffItem.get();
        const value = activeDiffItem?.entry?.value;
        if (!value) {
            return;
        }
        const target = value.modified ?? value.original;
        if (!target) {
            return;
        }
        target.setLanguage(languageId, source);
    }
    async getViewModel() {
        return this._viewModel.getPromise();
    }
    async _createModel() {
        const source = await this._resolvedSource.getPromise();
        const textResourceConfigurationService = this._textResourceConfigurationService;
        const garbage = ( (new DisposableStore()));
        const documentsWithPromises = mapObservableArrayCached(this, source.resources, async (r, store) => {
            let original;
            let modified;
            const store2 = ( (new DisposableStore()));
            store.add(toDisposable(() => {
                garbage.add(store2);
            }));
            try {
                [original, modified] = await Promise.all([
                    r.original ? this._textModelService.createModelReference(r.original) : undefined,
                    r.modified ? this._textModelService.createModelReference(r.modified) : undefined,
                ]);
                if (original) {
                    store2.add(original);
                }
                if (modified) {
                    store2.add(modified);
                }
            }
            catch (e) {
                console.error(e);
                onUnexpectedError(e);
                return undefined;
            }
            const uri = (r.modified ?? r.original);
            return (
                 (new ConstLazyPromise({
                    original: original?.object.textEditorModel,
                    modified: modified?.object.textEditorModel,
                    get options() {
                        return {
                            ...getReadonlyConfiguration(modified?.object.isReadonly() ?? true),
                            ...computeOptions(textResourceConfigurationService.getValue(uri)),
                        };
                    },
                    onOptionsDidChange: h => this._textResourceConfigurationService.onDidChangeConfiguration(e => {
                        if (e.affectsConfiguration(uri, 'editor') || e.affectsConfiguration(uri, 'diffEditor')) {
                            h();
                        }
                    }),
                }))
            );
        }, i => JSON.stringify([i.modified?.toString(), i.original?.toString()]));
        const documents = observableValue('documents', []);
        const updateDocuments = derived(async (reader) => {
            const docsPromises = documentsWithPromises.read(reader);
            const docs = await Promise.all(docsPromises);
            const newDocuments = docs.filter(isDefined);
            documents.set(newDocuments, undefined);
            garbage.clear();
        });
        const a = recomputeInitiallyAndOnChange(updateDocuments);
        await updateDocuments.get();
        const result = {
            dispose: () => {
                a.dispose();
                garbage.dispose();
            },
            documents: ( (new ValueWithChangeEventFromObservable(documents))),
            contextKeys: source.source?.contextKeys,
        };
        return result;
    }
    matches(otherInput) {
        if (super.matches(otherInput)) {
            return true;
        }
        if (otherInput instanceof MultiDiffEditorInput_1) {
            return ( (this.multiDiffSource.toString())) === ( (otherInput.multiDiffSource.toString()));
        }
        return false;
    }
    isDirty() { return this._isDirtyObservable.get(); }
    async save(group, options) {
        await this.doSaveOrRevert('save', group, options);
        return this;
    }
    revert(group, options) {
        return this.doSaveOrRevert('revert', group, options);
    }
    async doSaveOrRevert(mode, group, options) {
        const items = this._viewModel.currentValue?.items.get();
        if (items) {
            await Promise.all(( (items.map(async (item) => {
                const model = item.diffEditorViewModel.model;
                const handleOriginal = model.original.uri.scheme !== Schemas.untitled && this._textFileService.isDirty(model.original.uri);
                await Promise.all([
                    handleOriginal ? mode === 'save' ? this._textFileService.save(model.original.uri, options) : this._textFileService.revert(model.original.uri, options) : Promise.resolve(),
                    mode === 'save' ? this._textFileService.save(model.modified.uri, options) : this._textFileService.revert(model.modified.uri, options),
                ]);
            }))));
        }
        return undefined;
    }
};
MultiDiffEditorInput = MultiDiffEditorInput_1 = ( (__decorate([
    ( (__param(4, ITextModelService))),
    ( (__param(5, ITextResourceConfigurationService))),
    ( (__param(6, IInstantiationService))),
    ( (__param(7, IMultiDiffSourceResolverService))),
    ( (__param(8, ITextFileService)))
], MultiDiffEditorInput)));
function isUriDirty(textFileService, uri) {
    return observableFromEvent(Event.filter(textFileService.files.onDidChangeDirty, e => ( (e.resource.toString())) === ( (uri.toString()))), () => textFileService.isDirty(uri));
}
function getReadonlyConfiguration(isReadonly) {
    return {
        readOnly: !!isReadonly,
        readOnlyMessage: typeof isReadonly !== 'boolean' ? isReadonly : undefined
    };
}
function computeOptions(configuration) {
    const editorConfiguration = deepClone(configuration.editor);
    if (isObject(configuration.diffEditor)) {
        const diffEditorConfiguration = deepClone(configuration.diffEditor);
        diffEditorConfiguration.diffCodeLens = diffEditorConfiguration.codeLens;
        delete diffEditorConfiguration.codeLens;
        diffEditorConfiguration.diffWordWrap = diffEditorConfiguration.wordWrap;
        delete diffEditorConfiguration.wordWrap;
        Object.assign(editorConfiguration, diffEditorConfiguration);
    }
    return editorConfiguration;
}
let MultiDiffEditorResolverContribution = class MultiDiffEditorResolverContribution extends Disposable {
    static { this.ID = 'workbench.contrib.multiDiffEditorResolver'; }
    constructor(editorResolverService, instantiationService) {
        super();
        this._register(editorResolverService.registerEditor(`*`, {
            id: DEFAULT_EDITOR_ASSOCIATION.id,
            label: DEFAULT_EDITOR_ASSOCIATION.displayName,
            detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
            priority: RegisteredEditorPriority.builtin
        }, {}, {
            createMultiDiffEditorInput: (multiDiffEditor) => {
                return {
                    editor: MultiDiffEditorInput.fromResourceMultiDiffEditorInput(multiDiffEditor, instantiationService),
                };
            },
        }));
    }
};
MultiDiffEditorResolverContribution = ( (__decorate([
    ( (__param(0, IEditorResolverService))),
    ( (__param(1, IInstantiationService)))
], MultiDiffEditorResolverContribution)));
class MultiDiffEditorSerializer {
    canSerialize(editor) {
        return editor instanceof MultiDiffEditorInput && !editor.isTransient;
    }
    serialize(editor) {
        if (!this.canSerialize(editor)) {
            return undefined;
        }
        return JSON.stringify(editor.serialize());
    }
    deserialize(instantiationService, serializedEditor) {
        try {
            const data = parse(serializedEditor);
            return MultiDiffEditorInput.fromSerialized(data, instantiationService);
        }
        catch (err) {
            onUnexpectedError(err);
            return undefined;
        }
    }
}
export { MultiDiffEditorInput, MultiDiffEditorResolverContribution, MultiDiffEditorSerializer };
