/**
 * CodeEditor - CodeMirror 6 wrapper with Emmet support
 */
import { EditorState, EditorSelection, Prec, StateField, Compartment } from "@codemirror/state";
import { EditorView, keymap, placeholder, Decoration } from "@codemirror/view";
import { defaultKeymap, historyKeymap, indentMore, indentLess, undo, redo } from "@codemirror/commands";
import { history } from "@codemirror/commands";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
import { markdown } from "@codemirror/lang-markdown";
import { javascript } from "@codemirror/lang-javascript";
import { autocompletion } from "@codemirror/autocomplete";
import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags } from "@lezer/highlight";

// Custom theme with pink accent colors (matching app completed state)
const crispyTheme = EditorView.theme(
	{
		"&": {
			backgroundColor: "#262630",
			color: "#c8c8d0"
		},
		".cm-content": {
			caretColor: "#d46d9b"
		},
		".cm-cursor, .cm-dropCursor": {
			borderLeftColor: "#d46d9b"
		},
		"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
			backgroundColor: "#3e3e4a"
		},
		".cm-panels": {
			backgroundColor: "#262630",
			color: "#c8c8d0"
		},
		".cm-searchMatch": {
			backgroundColor: "#3e3e4a",
			outline: "1px solid #d46d9b"
		},
		".cm-searchMatch.cm-searchMatch-selected": {
			backgroundColor: "rgba(212, 109, 155, 0.3)"
		},
		".cm-activeLine": {
			backgroundColor: "#2e2e3a"
		},
		".cm-selectionMatch": {
			backgroundColor: "#3e3e4a"
		},
		".cm-gutters": {
			backgroundColor: "#262630",
			color: "#808090",
			border: "none"
		},
		".cm-activeLineGutter": {
			backgroundColor: "#2e2e3a"
		},
		".cm-lineNumbers .cm-gutterElement": {
			color: "#808090"
		}
	},
	{ dark: true }
);

// Default syntax highlighting (blue accent)
const defaultHighlight = HighlightStyle.define([
	{ tag: tags.keyword, color: "#eba6c9" },
	{ tag: tags.operator, color: "#cdd6f4" },
	{ tag: tags.variableName, color: "#89b4fa" },
	{ tag: tags.propertyName, color: "#89b4fa" },
	{ tag: tags.attributeName, color: "#89b4fa" },
	{ tag: tags.className, color: "#89b4fa" },
	{ tag: tags.tagName, color: "#eba6c9" },
	{ tag: tags.string, color: "#a6e3a1" },
	{ tag: tags.number, color: "#fab387" },
	{ tag: tags.bool, color: "#fab387" },
	{ tag: tags.null, color: "#fab387" },
	{ tag: tags.comment, color: "#6c7086", fontStyle: "italic" },
	{ tag: tags.bracket, color: "#cdd6f4" },
	{ tag: tags.punctuation, color: "#cdd6f4" },
	{ tag: tags.definition(tags.variableName), color: "#89b4fa" },
	{ tag: tags.function(tags.variableName), color: "#89b4fa" },
	{ tag: tags.atom, color: "#eba6c9" },
	{ tag: tags.unit, color: "#a6e3a1" },
	{ tag: tags.color, color: "#f9e2af" }
]);

// CSS section highlighting (pink selectors)
const cssHighlight = HighlightStyle.define([
	{ tag: tags.keyword, color: "#eba6c9" },
	{ tag: tags.operator, color: "#cdd6f4" },
	{ tag: tags.variableName, color: "#eba6c9" },
	{ tag: tags.propertyName, color: "#89b4fa" },
	{ tag: tags.attributeName, color: "#89b4fa" },
	{ tag: tags.className, color: "#eba6c9" },
	{ tag: tags.tagName, color: "#eba6c9" },
	{ tag: tags.string, color: "#a6e3a1" },
	{ tag: tags.number, color: "#fab387" },
	{ tag: tags.bool, color: "#fab387" },
	{ tag: tags.null, color: "#fab387" },
	{ tag: tags.comment, color: "#6c7086", fontStyle: "italic" },
	{ tag: tags.bracket, color: "#cdd6f4" },
	{ tag: tags.punctuation, color: "#cdd6f4" },
	{ tag: tags.definition(tags.variableName), color: "#eba6c9" },
	{ tag: tags.function(tags.variableName), color: "#89b4fa" },
	{ tag: tags.atom, color: "#eba6c9" },
	{ tag: tags.unit, color: "#a6e3a1" },
	{ tag: tags.color, color: "#f9e2af" }
]);

// Get highlight style based on section
function getHighlightForSection(section) {
	if (section === "css") return cssHighlight;
	return defaultHighlight;
}

// Get theme with section-specific highlighting
export function getEditorTheme(section) {
	return [crispyTheme, syntaxHighlighting(getHighlightForSection(section))];
}

// Default combined theme export (for backwards compatibility)
export const crispyEditorTheme = [crispyTheme, syntaxHighlighting(defaultHighlight)];

// Custom overrides for editor styling
const editorTheme = EditorView.theme(
	{
		"&": {
			height: "100%",
			fontSize: "14px"
		},
		".cm-content": {
			fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
			padding: "12px 0"
		},
		".cm-line": {
			padding: "0 12px"
		}
	},
	{ dark: true }
);

export class CodeEditor {
	constructor(container, options = {}) {
		this.container = container;
		this.options = options;
		this.view = null;
		this.mode = options.mode || "css";
		this.section = options.section || null;
		this.onChange = options.onChange || (() => {});
		// Read-only zones support
		this.prefixLength = 0;
		this.suffixLength = 0;
		this.currentPrefix = "";
		this.currentSuffix = "";
		this.readOnlyCompartment = new Compartment();
	}

	/**
	 * Initialize the editor (backwards compatible wrapper)
	 */
	init(initialValue = "") {
		return this.initWithContext("", initialValue, "");
	}

	/**
	 * Initialize the editor with read-only prefix/suffix zones
	 * @param {string} prefix - Read-only prefix text (e.g., ".card {\n  ")
	 * @param {string} initialValue - Editable user code
	 * @param {string} suffix - Read-only suffix text (e.g., "\n}")
	 */
	initWithContext(prefix = "", initialValue = "", suffix = "") {
		// Clear container
		this.container.innerHTML = "";

		// Store prefix/suffix for re-initialization (e.g., when mode changes)
		this.currentPrefix = prefix;
		this.currentSuffix = suffix;
		this.prefixLength = prefix.length;
		this.suffixLength = suffix.length;

		const fullDoc = prefix + initialValue + suffix;

		// Get language extension based on mode
		const langExtension =
			this.mode === "html" ? html() : this.mode === "javascript" ? javascript() : this.mode === "markdown" ? markdown() : css();

		// Create read-only zones decorations
		const readOnlyMark = Decoration.mark({ class: "cm-readonly-zone" });

		// StateField to track and provide decorations for read-only zones
		const readOnlyDecorations = StateField.define({
			create: (state) => {
				const decorations = [];
				if (this.prefixLength > 0) {
					decorations.push(readOnlyMark.range(0, this.prefixLength));
				}
				if (this.suffixLength > 0) {
					const suffixStart = state.doc.length - this.suffixLength;
					decorations.push(readOnlyMark.range(suffixStart, state.doc.length));
				}
				return Decoration.set(decorations);
			},
			update: (decorations, tr) => {
				if (!tr.docChanged) return decorations;
				// Recalculate decorations after document changes
				const newDecorations = [];
				if (this.prefixLength > 0) {
					newDecorations.push(readOnlyMark.range(0, this.prefixLength));
				}
				if (this.suffixLength > 0) {
					const suffixStart = tr.state.doc.length - this.suffixLength;
					newDecorations.push(readOnlyMark.range(suffixStart, tr.state.doc.length));
				}
				return Decoration.set(newDecorations);
			},
			provide: (f) => EditorView.decorations.from(f)
		});

		// Change filter to prevent edits in read-only zones
		const readOnlyFilter = EditorState.changeFilter.of((tr) => {
			// If no prefix/suffix, allow all changes
			if (this.prefixLength === 0 && this.suffixLength === 0) {
				return true;
			}

			const prefixEnd = this.prefixLength;
			const suffixStart = tr.startState.doc.length - this.suffixLength;

			// Check all change ranges - allow only changes within [prefixEnd, suffixStart]
			let blocked = false;
			tr.changes.iterChangedRanges((fromA, toA) => {
				// Block if change starts in prefix zone
				if (fromA < prefixEnd) {
					blocked = true;
				}
				// Block if change extends into suffix zone
				if (toA > suffixStart) {
					blocked = true;
				}
			});

			return !blocked;
		});

		// Transaction filter to constrain cursor/selection to editable area
		const cursorFilter = EditorState.transactionFilter.of((tr) => {
			// If no prefix/suffix, no constraints needed
			if (this.prefixLength === 0 && this.suffixLength === 0) {
				return tr;
			}

			const prefixEnd = this.prefixLength;
			const suffixStart = tr.newDoc.length - this.suffixLength;

			// Check if selection needs adjustment
			const selection = tr.newSelection;
			let needsAdjustment = false;

			for (const range of selection.ranges) {
				if (range.from < prefixEnd || range.to > suffixStart) {
					needsAdjustment = true;
					break;
				}
			}

			if (!needsAdjustment) {
				return tr;
			}

			// Clamp selection to editable area
			const newRanges = selection.ranges.map((range) => {
				const from = Math.max(prefixEnd, Math.min(suffixStart, range.from));
				const to = Math.max(prefixEnd, Math.min(suffixStart, range.to));
				return EditorSelection.range(from, to);
			});

			return [tr, { selection: EditorSelection.create(newRanges, selection.mainIndex) }];
		});

		// Build extensions array
		const extensions = [
			langExtension,
			getEditorTheme(this.section),
			editorTheme,
			// History for undo/redo
			history(),
			// Read-only zones (decorations, change filter, and cursor constraint)
			readOnlyDecorations,
			readOnlyFilter,
			cursorFilter,
			// Emmet abbreviation tracking
			abbreviationTracker(),
			// High priority keymap for Emmet
			Prec.highest(
				keymap.of([
					{
						key: "Tab",
						run: expandAbbreviation
					}
				])
			),
			// Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z)
			keymap.of([...historyKeymap, { key: "Tab", run: indentMore }, { key: "Shift-Tab", run: indentLess }, ...defaultKeymap]),
			autocompletion({
				activateOnTyping: true,
				maxRenderedOptions: 10
			}),
			EditorView.updateListener.of((update) => {
				if (update.docChanged) {
					// Report only the editable portion to the onChange handler
					this.onChange(this.getEditableValue());
				}
			}),
			EditorView.lineWrapping
		];

		// Add placeholder if provided (only makes sense when no prefix/suffix)
		if (this.options.placeholder && this.prefixLength === 0 && this.suffixLength === 0) {
			extensions.push(placeholder(this.options.placeholder));
		}

		// Create editor state
		const state = EditorState.create({
			doc: fullDoc,
			extensions
		});

		// Create editor view
		this.view = new EditorView({
			state,
			parent: this.container
		});

		// Position cursor at start of editable area
		if (this.prefixLength > 0) {
			this.view.dispatch({
				selection: { anchor: this.prefixLength }
			});
		}

		return this;
	}

	/**
	 * Get current full editor value (including prefix/suffix)
	 */
	getValue() {
		return this.view ? this.view.state.doc.toString() : "";
	}

	/**
	 * Get only the editable portion (excluding prefix/suffix)
	 */
	getEditableValue() {
		if (!this.view) return "";
		const fullText = this.view.state.doc.toString();
		const editableEnd = fullText.length - this.suffixLength;
		return fullText.slice(this.prefixLength, editableEnd);
	}

	/**
	 * Set editor value in the editable zone only (preserves history)
	 */
	setValue(value) {
		if (!this.view) return;

		// Only replace the editable portion
		const editableStart = this.prefixLength;
		const editableEnd = this.view.state.doc.length - this.suffixLength;

		this.view.dispatch({
			changes: {
				from: editableStart,
				to: editableEnd,
				insert: value
			}
		});
	}

	/**
	 * Set editor value and clear history (for lesson switching)
	 * @param {string} value - The editable user code (not including prefix/suffix)
	 * @param {string} prefix - Optional read-only prefix
	 * @param {string} suffix - Optional read-only suffix
	 */
	setValueAndClearHistory(value, prefix = "", suffix = "") {
		this.initWithContext(prefix, value, suffix);
	}

	/**
	 * Set editor mode (html or css)
	 */
	setMode(mode) {
		if (this.mode === mode) return;

		this.mode = mode;
		const editableValue = this.getEditableValue();
		this.initWithContext(this.currentPrefix, editableValue, this.currentSuffix);
	}

	/**
	 * Set section for theme (css, html, tailwind)
	 */
	setSection(section) {
		if (this.section === section) return;

		this.section = section;
		const editableValue = this.getEditableValue();
		this.initWithContext(this.currentPrefix, editableValue, this.currentSuffix);
	}

	/**
	 * Focus the editor
	 */
	focus() {
		if (this.view) {
			this.view.focus();
		}
	}

	/**
	 * Undo last change
	 */
	undo() {
		if (this.view) {
			undo(this.view);
		}
	}

	/**
	 * Redo last undone change
	 */
	redo() {
		if (this.view) {
			redo(this.view);
		}
	}

	/**
	 * Destroy the editor
	 */
	destroy() {
		if (this.view) {
			this.view.destroy();
			this.view = null;
		}
	}
}
