var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { UmbBlockGridEntriesContext } from './block-grid-entries.context.js';
import { css, customElement, html, ifDefined, nothing, property, repeat, state, when, } from '../../../../../external/lit/index.js';
import { getAccumulatedValueOfIndex, getInterpolatedIndexOfPositionInWeightMap, isWithinRect, } from '../../../../core/utils/index.js';
import { UmbFormControlMixin, UmbFormControlValidator } from '../../../../core/validation/index.js';
import { UmbLitElement } from '../../../../core/lit-element/index.js';
import { UmbSorterController } from '../../../../core/sorter/index.js';
import { UmbTextStyles } from '../../../../core/style/index.js';
/**
 * Notice this utility method is not really shareable with others as it also takes areas into account. [NL]
 * @param args
 * @returns { null | true }
 */
function resolvePlacementAsBlockGrid(args) {
    // If this has areas, we do not want to move, unless we are at the edge
    if (args.relatedModel.areas &&
        args.relatedModel.areas.length > 0 &&
        isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) {
        return null;
    }
    /** We need some data about the grid to figure out if there is room to be placed next to the found element */
    const approvedContainerComputedStyles = getComputedStyle(args.containerElement);
    const gridColumnGap = Number(approvedContainerComputedStyles.columnGap.split('px')[0]) || 0;
    const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue('--umb-block-grid--grid-columns'), 10);
    const foundElColumns = parseInt(args.relatedElement.dataset.colSpan ?? '', 10);
    const currentElementColumns = args.item.columnSpan;
    if (currentElementColumns >= gridColumnNumber) {
        return true;
    }
    // Get grid template:
    const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns
        .trim()
        .split('px')
        .map((x) => Number(x))
        .filter((n) => n > 0)
        .map((n, i, list) => (list.length === i ? n : n + gridColumnGap));
    // ensure all columns are there.
    // This will also ensure handling non-css-grid mode,
    // use container width divided by amount of columns( or the item width divided by its amount of columnSpan)
    let amountOfColumnsInWeightMap = approvedContainerGridColumns.length;
    const amountOfUnknownColumns = gridColumnNumber - amountOfColumnsInWeightMap;
    if (amountOfUnknownColumns > 0) {
        const accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
        const missingColumnWidth = (args.containerRect.width - accumulatedValue) / amountOfUnknownColumns;
        if (missingColumnWidth > 0) {
            while (amountOfColumnsInWeightMap++ < gridColumnNumber) {
                approvedContainerGridColumns.push(missingColumnWidth);
            }
        }
    }
    let offsetPlacement = 0;
    /* If placeholder is in this same line, we want to assume that it will offset the placement of the found element,
    which provides more potential space for the item to drop at.
    This is relevant in this calculation where we look at the space to determine if its a vertical or horizontal drop in relation to the found element.
    */
    if (args.placeholderIsInThisRow && args.elementRect.left < args.relatedRect.left) {
        offsetPlacement = -(args.elementRect.width + gridColumnGap);
    }
    const relatedStartX = Math.max(args.relatedRect.left - args.containerRect.left + offsetPlacement, 0);
    const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
    // If the found related element does not have enough room after which for the current element, then we go vertical mode:
    const verticalDirection = relatedStartCol + foundElColumns + currentElementColumns > gridColumnNumber;
    return verticalDirection;
    /*
    let placeAfter = args.horizontalPlaceAfter;

    return {
        verticalDirection,
        placeAfter,
    };*/
}
// --------------------------
// End of utils.
// --------------------------
const SORTER_CONFIG = {
    getUniqueOfElement: (element) => {
        return element.contentKey;
    },
    getUniqueOfModel: (modelEntry) => {
        return modelEntry.contentKey;
    },
    resolvePlacement: resolvePlacementAsBlockGrid,
    identifier: 'block-grid-editor',
    itemSelector: 'umb-block-grid-entry',
    containerSelector: '.umb-block-grid__layout-container',
};
/**
 * @element umb-block-grid-entries
 */
let UmbBlockGridEntriesElement = class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElement) {
    #sorter;
    #context;
    #controlValidator;
    #typeLimitValidator;
    #rangeUnderflowValidator;
    #rangeOverflowValidator;
    set areaKey(value) {
        this._areaKey = value;
        this.#context.setAreaKey(value ?? null);
        this.#controlValidator?.destroy();
        if (this.areaKey) {
            // Only when there is a area key we should create a validator, otherwise it is the root entries element, which is taking part of the Property Editor Form Control. [NL]
            // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL]
            this.#controlValidator = new UmbFormControlValidator(this, this);
            //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias == 'my-input-alias')].value");
        }
    }
    get areaKey() {
        return this._areaKey;
    }
    set layoutColumns(value) {
        this.#context.setLayoutColumns(value);
    }
    get layoutColumns() {
        return this.#context.getLayoutColumns();
    }
    constructor() {
        super();
        this.#sorter = new UmbSorterController(this, {
            ...SORTER_CONFIG,
            onStart: () => {
                this.#context.onDragStart();
            },
            onEnd: () => {
                this.#context.onDragEnd();
            },
            onChange: ({ model }) => {
                this.#context.setLayouts(model);
            },
            onRequestMove: ({ item }) => {
                return this.#context.allowDrop(item.contentKey);
            },
            onDisallowed: () => {
                this.setAttribute('disallow-drop', '');
            },
            onAllowed: () => {
                this.removeAttribute('disallow-drop');
            },
        });
        this.#context = new UmbBlockGridEntriesContext(this);
        this._isReadOnly = false;
        this._layoutEntries = [];
        this.observe(this.#context.layoutEntries, (layoutEntries) => {
            //const oldValue = this._layoutEntries;
            this.#sorter.setModel(layoutEntries);
            this._layoutEntries = layoutEntries;
            //this.requestUpdate('layoutEntries', oldValue);
        }, null);
        this.observe(this.#context.allowedBlockTypes, (allowedBlockTypes) => {
            this._allowedBlockTypes = allowedBlockTypes;
            if (allowedBlockTypes.length === 1) {
                this.observe(this.#context.firstAllowedBlockTypeName(), (firstAllowedName) => {
                    this._createLabel = this.localize.term('blockEditor_addThis', this.localize.string(firstAllowedName));
                }, 'observeSingleBlockTypeName');
            }
            else {
                this.removeUmbControllerByAlias('observeSingleBlockTypeName');
                this._createLabel = this.localize.term('blockEditor_addBlock');
            }
        }, null);
        this.observe(this.#context.rangeLimits, (rangeLimits) => {
            this.#setupRangeValidation(rangeLimits);
        }, null);
        this.observe(this.#context.hasTypeLimits, (hasTypeLimits) => {
            this.#setupBlockTypeLimitValidation(hasTypeLimits);
        }, null);
        this.#context.getManager().then((manager) => {
            this.observe(manager.layoutStylesheet, (stylesheet) => {
                if (!stylesheet || this._styleElement?.href === stylesheet)
                    return;
                this._styleElement = document.createElement('link');
                this._styleElement.rel = 'stylesheet';
                this._styleElement.href = stylesheet;
            }, 'observeStylesheet');
            this.observe(manager.readOnlyState.permitted, (isReadOnly) => (this._isReadOnly = isReadOnly), 'observeIsReadOnly');
            this.observe(manager.variantId, (variantId) => {
                if (variantId) {
                    this.#sorter.identifier = 'umb-block-grid-' + variantId.toString();
                }
            }, 'observeVariantId');
            if (this.areaKey) {
                this.observe(this.#context.areaTypeCreateLabel, (label) => (this._configCreateLabel = label), 'observeConfigCreateLabel');
            }
            else {
                this.observe(manager.editorConfigurationPart((x) => x?.find((y) => y.alias === 'createLabel')?.value), (label) => (this._configCreateLabel = label), 'observeConfigCreateLabel');
            }
        });
        new UmbFormControlValidator(this, this /*, this.#dataPath*/);
    }
    async #setupRangeValidation(rangeLimit) {
        this._limitMax = rangeLimit?.max;
        if (this.#rangeUnderflowValidator) {
            this.removeValidator(this.#rangeUnderflowValidator);
            this.#rangeUnderflowValidator = undefined;
        }
        if (rangeLimit?.min !== 0) {
            this.#rangeUnderflowValidator = this.addValidator('rangeUnderflow', () => {
                return this.localize.term('validation_entriesShort', rangeLimit.min, (rangeLimit.min ?? 0) - this._layoutEntries.length);
            }, () => this._layoutEntries.length < (rangeLimit?.min ?? 0));
        }
        if (this.#rangeOverflowValidator) {
            this.removeValidator(this.#rangeOverflowValidator);
            this.#rangeOverflowValidator = undefined;
        }
        if (rangeLimit?.max !== Infinity) {
            this.#rangeOverflowValidator = this.addValidator('rangeOverflow', () => {
                return this.localize.term('validation_entriesExceed', rangeLimit.max, this._layoutEntries.length - (rangeLimit.max ?? this._layoutEntries.length));
            }, () => this._layoutEntries.length > (rangeLimit?.max ?? Infinity));
        }
    }
    async #setupBlockTypeLimitValidation(hasTypeLimits) {
        if (this.#typeLimitValidator) {
            this.removeValidator(this.#typeLimitValidator);
            this.#typeLimitValidator = undefined;
        }
        if (hasTypeLimits) {
            // If we have specific block type limits, we should use those for validation (not the Block Type Configurations)
            this.#typeLimitValidator = this.addValidator('customError', () => {
                const invalids = this.#context.getInvalidBlockTypeLimits();
                return invalids
                    .map((invalidRule) => this.localize.term(invalidRule.amount < invalidRule.minRequirement
                    ? 'blockEditor_areaValidationEntriesShort'
                    : 'blockEditor_areaValidationEntriesExceed', invalidRule.name, invalidRule.amount, invalidRule.minRequirement, invalidRule.maxRequirement))
                    .join(', ');
            }, () => {
                return !this.#context.checkBlockTypeLimitsValidity();
            });
        }
        else {
            // Limit based on Block Type Configurations (Allow in Areas / allow in root)
            this.#typeLimitValidator = this.addValidator('customError', () => {
                const invalids = this.#context
                    .getInvalidBlockTypeConfigurations()
                    // make invalids unique:
                    .filter((v, i, a) => a.indexOf(v) === i)
                    // join them together to become a string:
                    .join(', ');
                return this.localize.term(this._areaKey
                    ? 'blockEditor_areaValidationEntriesNotAllowed'
                    : 'blockEditor_rootValidationEntriesNotAllowed', invalids);
            }, () => {
                return !this.#context.checkBlockTypeConfigurationValidity();
            });
        }
    }
    // TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type. [NL]
    render() {
        return html `
			${this._styleElement}
			<div class="umb-block-grid__layout-container" data-area-length=${this._layoutEntries.length}>
				${repeat(this._layoutEntries, (layout) => layout.contentKey, (layout, index) => html `<umb-block-grid-entry
							class="umb-block-grid__layout-item"
							index=${index}
							.contentKey=${layout.contentKey}
							.layout=${layout}>
						</umb-block-grid-entry>
					`)}
			</div>
			${when(this._allowedBlockTypes && this._allowedBlockTypes.length > 0, () => this.#renderCreateButtonGroup())}
			${when(this._areaKey, () => html `<uui-form-validation-message .for=${this}></uui-form-validation-message>`)}
		`;
    }
    #renderCreateButtonGroup() {
        if (this._limitMax === 1 && this._layoutEntries.length > 0)
            return nothing;
        if (this._areaKey === null || this._layoutEntries.length === 0) {
            return html `
				<uui-button-group id="createButton">
					${this.#renderCreateButton()} ${this.#renderPasteButton()}
				</uui-button-group>
			`;
        }
        else if (this._isReadOnly === false) {
            return html `
				<uui-button-inline-create
					href=${this.#context.getPathForCreateBlock(-1) ?? ''}
					label=${this.localize.term('blockEditor_addBlock')}></uui-button-inline-create>
			`;
        }
        else {
            return nothing;
        }
    }
    #renderCreateButton() {
        if (this._isReadOnly && this._layoutEntries.length > 0)
            return nothing;
        const createPath = this.#context.getPathForCreateBlock(-1);
        return html `
			<uui-button
				look="placeholder"
				color=${this.pristine === false && this.validity.valid === false ? 'invalid' : 'default'}
				label=${this._configCreateLabel ?? this._createLabel ?? ''}
				href=${ifDefined(createPath)}
				@click=${async () => {
            // If no path, then we can conclude there is not modal flow for the user to follow, instead we will just insert the Block: [NL]
            if (createPath === undefined) {
                if (!this._allowedBlockTypes || this._allowedBlockTypes.length === 0) {
                    throw new Error('No block types are configured for this Block List property editor');
                }
                const areaKey = this._areaKey;
                const parentUnique = this.#context.getParentUnique();
                if (areaKey === undefined || parentUnique === undefined) {
                    throw new Error('Cannot create block without a defined areaKey and parentUnique');
                }
                const originData = {
                    index: -1,
                    areaKey: areaKey ?? undefined,
                    parentUnique: parentUnique,
                };
                const created = await this.#context.create(this._allowedBlockTypes[0].contentElementTypeKey, 
                // We can parse an empty object, cause the rest will be filled in by others.
                {}, originData);
                if (created) {
                    this.#context.insert(created.layout, created.content, created.settings, originData);
                }
                else {
                    throw new Error('Failed to create block');
                }
            }
        }}
				?disabled=${this._isReadOnly}></uui-button>
		`;
    }
    #renderPasteButton() {
        if (this._areaKey)
            return nothing;
        if (this._isReadOnly && this._layoutEntries.length > 0)
            return nothing;
        return html `
			<uui-button
				label=${this.localize.term('content_createFromClipboard')}
				look="placeholder"
				href=${this.#context.getPathForClipboard(-1) ?? ''}
				?disabled=${this._isReadOnly}
				title=${this.localize.term('general_clipboard')}>
				<uui-icon name="icon-clipboard-paste"></uui-icon>
			</uui-button>
		`;
    }
    static { this.styles = [
        UmbTextStyles,
        css `
			:host {
				position: relative;
				display: grid;
			}
			:host([disallow-drop])::before {
				content: '';
				position: absolute;
				z-index: 1;
				inset: 0;
				border: 2px solid var(--uui-color-danger);
				border-radius: calc(var(--uui-border-radius) * 2);
				pointer-events: none;
			}
			:host([disallow-drop])::after {
				content: '';
				position: absolute;
				z-index: 1;
				inset: 0;
				border-radius: calc(var(--uui-border-radius) * 2);
				background-color: var(--uui-color-danger);
				opacity: 0.2;
				pointer-events: none;
			}

			> div {
				display: flex;
				flex-direction: column;
				align-items: stretch;
			}

			#createButton {
				margin-top: 1px;
				grid-template-columns: 1fr auto;
				display: grid;
			}

			/* Only when we are n an area, we like to hide the button on drag */
			:host([area-key]) #createButton {
				--umb-block-grid--create-button--is-dragging--variable: var(--umb-block-grid--is-dragging) none;
				display: var(--umb-block-grid--create-button--is-dragging--variable, grid);
			}

			.umb-block-grid__layout-container[data-area-length='0'] {
				--umb-block-grid--layout-container--is-dragging--variable: var(--umb-block-grid--is-dragging) 1;
				min-height: calc(var(--umb-block-grid--layout-container--is-dragging--variable, 0) * var(--uui-size-11));
			}

			.umb-block-grid__layout-container[data-area-length='0']::after {
				content: '';
				position: absolute;
				inset: 0;
				top: 1px;
				border: calc(var(--umb-block-grid--layout-container--is-dragging--variable, 0) * 1px) dashed
					var(--uui-color-border);
				border-radius: var(--uui-border-radius);
			}
		`,
    ]; }
};
__decorate([
    property({ type: String, attribute: 'area-key', reflect: true })
], UmbBlockGridEntriesElement.prototype, "areaKey", null);
__decorate([
    property({ attribute: false })
], UmbBlockGridEntriesElement.prototype, "layoutColumns", null);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_areaKey", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_allowedBlockTypes", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_configCreateLabel", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_createLabel", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_isReadOnly", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_layoutEntries", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_limitMax", void 0);
__decorate([
    state()
], UmbBlockGridEntriesElement.prototype, "_styleElement", void 0);
UmbBlockGridEntriesElement = __decorate([
    customElement('umb-block-grid-entries')
], UmbBlockGridEntriesElement);
export { UmbBlockGridEntriesElement };
export default UmbBlockGridEntriesElement;
