/**
 * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
 */

import type { Editor } from '@ckeditor/ckeditor5-core';
import type { ModelDocumentSelection, Marker, ModelPosition, ModelRange, Model } from '@ckeditor/ckeditor5-engine';

/**
 * @module restricted-editing/restrictededitingmode/utils
 */

/**
 * Returns a single "restricted-editing-exception" marker at a given position. Contrary to
 * {@link module:engine/model/markercollection~MarkerCollection#getMarkersAtPosition}, it returnd a marker also when the postion is
 * equal to one of the marker's start or end positions.
 *
 * @internal
 */
export function getMarkerAtPosition( editor: Editor, position: ModelPosition ): Marker | undefined {
	for ( const marker of editor.model.markers ) {
		const markerRange = getExceptionRange( marker, editor.model );

		if ( isPositionInRangeBoundaries( markerRange, position ) ) {
			if ( marker.name.startsWith( 'restrictedEditingException:' ) ) {
				return marker;
			}
		}
	}
}

/**
 * Checks if the position is fully contained in the range. Positions equal to range start or end are considered "in".
 *
 * @internal
 */
export function isPositionInRangeBoundaries( range: ModelRange, position: ModelPosition ): boolean {
	return (
		range.containsPosition( position ) ||
		range.end.isEqual( position ) ||
		range.start.isEqual( position )
	);
}

/**
 * Checks if the selection is fully contained in the marker. Positions on marker boundaries are considered "in".
 *
 * ```xml
 * <marker>[]foo</marker> -> true
 * <marker>f[oo]</marker> -> true
 * <marker>f[oo</marker> ba]r -> false
 * <marker>foo</marker> []bar -> false
 * ```
 *
 * @internal
 */
export function isSelectionInMarker( selection: ModelDocumentSelection, model: Model, marker?: Marker ): boolean {
	if ( !marker ) {
		return false;
	}

	const markerRange = getExceptionRange( marker, model );

	if ( selection.isCollapsed ) {
		return isPositionInRangeBoundaries( markerRange, selection.focus! );
	}

	return markerRange.containsRange( selection.getFirstRange()!, true );
}

/**
 * Returns the marker range asjusted to the inside of exception wrapper element if needed.
 *
 * @internal
 */
export function getExceptionRange( marker: Marker, model: Model ): ModelRange {
	const markerRange = marker.getRange();
	const wrapperElement = markerRange.getContainedElement();

	if ( wrapperElement && wrapperElement.is( 'element', 'restrictedEditingException' ) ) {
		return model.createRangeIn( wrapperElement );
	}

	return markerRange;
}
