/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { n } from '../../../../../../../base/browser/dom.js';
import { Disposable } from '../../../../../../../base/common/lifecycle.js';
import { IObservable, derived, constObservable, IReader, autorun, observableValue } from '../../../../../../../base/common/observable.js';
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js';
import { ICodeEditor } from '../../../../../../browser/editorBrowser.js';
import { ObservableCodeEditor, observableCodeEditor } from '../../../../../../browser/observableCodeEditor.js';
import { EmbeddedCodeEditorWidget } from '../../../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js';
import { IDimension } from '../../../../../../common/core/2d/dimension.js';
import { Range } from '../../../../../../common/core/range.js';
import { LineRange } from '../../../../../../common/core/ranges/lineRange.js';
import { OffsetRange } from '../../../../../../common/core/ranges/offsetRange.js';
import { DetailedLineRangeMapping } from '../../../../../../common/diff/rangeMapping.js';
import { IModelDeltaDecoration, ITextModel } from '../../../../../../common/model.js';
import { ModelDecorationOptions } from '../../../../../../common/model/textModel.js';
import { InlineCompletionContextKeys } from '../../../controller/inlineCompletionContextKeys.js';
import { InlineEditsGutterIndicator, InlineEditsGutterIndicatorData, InlineSuggestionGutterMenuData, SimpleInlineSuggestModel } from '../components/gutterIndicatorView.js';
import { InlineEditTabAction } from '../inlineEditsViewInterface.js';
import { classNames, maxContentWidthInRange } from '../utils/utils.js';

export interface ILongDistancePreviewProps {
	diff: DetailedLineRangeMapping[];
	model: SimpleInlineSuggestModel;
	suggestInfo: InlineSuggestionGutterMenuData;
}

export class LongDistancePreviewEditor extends Disposable {
	public readonly previewEditor;
	private readonly _previewEditorObs;

	private readonly _previewRef = n.ref<HTMLDivElement>();
	public readonly element = n.div({ class: 'preview', style: { /*pointerEvents: 'none'*/ }, ref: this._previewRef });

	private _parentEditorObs: ObservableCodeEditor;

	constructor(
		private readonly _previewTextModel: ITextModel,
		private readonly _properties: IObservable<ILongDistancePreviewProps | undefined>,
		private readonly _parentEditor: ICodeEditor,
		private readonly _tabAction: IObservable<InlineEditTabAction>,
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
	) {
		super();

		this.previewEditor = this._register(this._createPreviewEditor());
		this._parentEditorObs = observableCodeEditor(this._parentEditor);

		this._register(autorun(reader => {
			this.previewEditor.setModel(this._state.read(reader)?.textModel || null);
		}));

		this._previewEditorObs = observableCodeEditor(this.previewEditor);
		this._register(this._previewEditorObs.setDecorations(derived(reader => {
			const state = this._state.read(reader);
			const decorations = this._editorDecorations.read(reader);
			return (state?.mode === 'original' ? decorations?.originalDecorations : decorations?.modifiedDecorations) ?? [];
		})));

		this._register(this._instantiationService.createInstance(
			InlineEditsGutterIndicator,
			this._previewEditorObs,
			derived(reader => {
				const state = this._properties.read(reader);
				if (!state) { return undefined; }
				return new InlineEditsGutterIndicatorData(
					state.suggestInfo,
					LineRange.ofLength(state.diff[0].original.startLineNumber, 1),
					state.model,
				);
			}),
			this._tabAction,
			constObservable(0),
			constObservable(false),
			observableValue(this, false),
		));

		this.updatePreviewEditorEffect.recomputeInitiallyAndOnChange(this._store);
	}

	private readonly _state = derived(this, reader => {
		const props = this._properties.read(reader);
		if (!props) {
			return undefined;
		}

		if (props.diff[0].innerChanges?.every(c => c.modifiedRange.isEmpty())) {
			return {
				diff: props.diff,
				visibleLineRange: LineRange.ofLength(props.diff[0].original.startLineNumber, 1),
				textModel: this._parentEditorObs.model.read(reader),
				mode: 'original' as const,
			};
		} else {
			return {
				diff: props.diff,
				visibleLineRange: LineRange.ofLength(props.diff[0].modified.startLineNumber, 1),
				textModel: this._previewTextModel,
				mode: 'modified' as const,
			};
		}
	});

	private _createPreviewEditor() {
		return this._instantiationService.createInstance(
			EmbeddedCodeEditorWidget,
			this._previewRef.element,
			{
				glyphMargin: false,
				lineNumbers: 'on',
				minimap: { enabled: false },
				guides: {
					indentation: false,
					bracketPairs: false,
					bracketPairsHorizontal: false,
					highlightActiveIndentation: false,
				},

				rulers: [],
				padding: { top: 0, bottom: 0 },
				//folding: false,
				selectOnLineNumbers: false,
				selectionHighlight: false,
				columnSelection: false,
				overviewRulerBorder: false,
				overviewRulerLanes: 0,
				//lineDecorationsWidth: 0,
				//lineNumbersMinChars: 0,
				revealHorizontalRightPadding: 0,
				bracketPairColorization: { enabled: true, independentColorPoolPerBracketType: false },
				scrollBeyondLastLine: false,
				scrollbar: {
					vertical: 'hidden',
					horizontal: 'hidden',
					handleMouseWheel: false,
				},
				readOnly: true,
				wordWrap: 'off',
				wordWrapOverride1: 'off',
				wordWrapOverride2: 'off',
			},
			{
				contextKeyValues: {
					[InlineCompletionContextKeys.inInlineEditsPreviewEditor.key]: true,
				},
				contributions: [],
			},
			this._parentEditor
		);
	}

	public readonly updatePreviewEditorEffect = derived(this, reader => {
		// this._widgetContent.readEffect(reader);
		this._previewEditorObs.model.read(reader); // update when the model is set

		const range = this._state.read(reader)?.visibleLineRange;
		if (!range) {
			return;
		}
		const hiddenAreas: Range[] = [];
		if (range.startLineNumber > 1) {
			hiddenAreas.push(new Range(1, 1, range.startLineNumber - 1, 1));
		}
		if (range.endLineNumberExclusive < this._previewTextModel.getLineCount() + 1) {
			hiddenAreas.push(new Range(range.endLineNumberExclusive, 1, this._previewTextModel.getLineCount() + 1, 1));
		}
		this.previewEditor.setHiddenAreas(hiddenAreas, undefined, true);
	});

	public readonly horizontalContentRangeInPreviewEditorToShow = derived(this, reader => {
		return this._getHorizontalContentRangeInPreviewEditorToShow(this.previewEditor, this._properties.read(reader)?.diff ?? [], reader);
	});

	public readonly previewEditorHeight = derived(this, (reader) => {
		const viewState = this._properties.read(reader);
		if (!viewState) {
			return constObservable(null);
		}

		const previewEditorHeight = this._previewEditorObs.observeLineHeightForLine(viewState.diff[0].modified.startLineNumber);
		return previewEditorHeight;
	}).flatten();

	private _getHorizontalContentRangeInPreviewEditorToShow(editor: ICodeEditor, diff: DetailedLineRangeMapping[], reader: IReader) {

		//diff[0].innerChanges[0].originalRange;
		const r = LineRange.ofLength(diff[0].modified.startLineNumber, 1);
		const l = this._previewEditorObs.layoutInfo.read(reader);
		const w = maxContentWidthInRange(this._previewEditorObs, r, reader) + l.contentLeft - l.verticalScrollbarWidth;
		const preferredRange = new OffsetRange(0, w);

		return {
			preferredRange,
			contentWidth: w,
		};
	}

	public layout(dimension: IDimension, desiredPreviewEditorScrollLeft: number): void {
		this.previewEditor.layout(dimension);
		this._previewEditorObs.editor.setScrollLeft(desiredPreviewEditorScrollLeft);
	}

	private readonly _editorDecorations = derived(this, reader => {
		const diff2 = this._state.read(reader);
		if (!diff2) { return undefined; }

		const diff = {
			mode: 'insertionInline' as const,
			diff: diff2.diff,
		};
		const originalDecorations: IModelDeltaDecoration[] = [];
		const modifiedDecorations: IModelDeltaDecoration[] = [];

		const diffWholeLineDeleteDecoration = ModelDecorationOptions.register({
			className: 'inlineCompletions-char-delete',
			description: 'char-delete',
			isWholeLine: false,
			zIndex: 1, // be on top of diff background decoration
		});

		const diffWholeLineAddDecoration = ModelDecorationOptions.register({
			className: 'inlineCompletions-char-insert',
			description: 'char-insert',
			isWholeLine: true,
		});

		const diffAddDecoration = ModelDecorationOptions.register({
			className: 'inlineCompletions-char-insert',
			description: 'char-insert',
			shouldFillLineOnLineBreak: true,
		});

		const hideEmptyInnerDecorations = true; // diff.mode === 'lineReplacement';
		for (const m of diff.diff) {
			if (m.modified.isEmpty || m.original.isEmpty) {
				if (!m.original.isEmpty) {
					originalDecorations.push({ range: m.original.toInclusiveRange()!, options: diffWholeLineDeleteDecoration });
				}
				if (!m.modified.isEmpty) {
					modifiedDecorations.push({ range: m.modified.toInclusiveRange()!, options: diffWholeLineAddDecoration });
				}
			} else {
				for (const i of m.innerChanges || []) {
					// Don't show empty markers outside the line range
					if (m.original.contains(i.originalRange.startLineNumber) && !(hideEmptyInnerDecorations && i.originalRange.isEmpty())) {
						originalDecorations.push({
							range: i.originalRange,
							options: {
								description: 'char-delete',
								shouldFillLineOnLineBreak: false,
								className: classNames(
									'inlineCompletions-char-delete',
									i.originalRange.isSingleLine() && diff.mode === 'insertionInline' && 'single-line-inline',
									i.originalRange.isEmpty() && 'empty',
								),
								zIndex: 1
							}
						});
					}
					if (m.modified.contains(i.modifiedRange.startLineNumber)) {
						modifiedDecorations.push({
							range: i.modifiedRange,
							options: diffAddDecoration
						});
					}
				}
			}
		}

		return { originalDecorations, modifiedDecorations };
	});
}
