import { UMB_PROPERTY_DATASET_CONTEXT } from '../../../core/property/index.js';
import { UmbContextBase } from '../../../../libs/class-api/index.js';
import { UmbBasicState, UmbBooleanState, classEqualMemoization, createObservablePart, mergeObservables, } from '../../../../libs/observable-api/index.js';
import { UmbVariantContext, UmbVariantId } from '../../../core/variant/index.js';
export class UmbElementPropertyDatasetContext extends UmbContextBase {
    #variantId;
    getVariantId() {
        return this.#variantId;
    }
    #propertyVariantIdPromise;
    #propertyVariantIdPromiseResolver;
    #propertyVariantIdMap;
    #variantContext;
    getEntityType() {
        return this._dataOwner.getEntityType();
    }
    getUnique() {
        return this._dataOwner.getUnique();
    }
    getReadOnly() {
        return this._readOnly.getValue();
    }
    constructor(host, dataOwner, variantId) {
        // The controller alias, is a very generic name cause we want only one of these for this controller host.
        super(host, UMB_PROPERTY_DATASET_CONTEXT);
        this.#propertyVariantIdMap = new UmbBasicState([]);
        this._propertyVariantIdMap = this.#propertyVariantIdMap.asObservable();
        this._readOnly = new UmbBooleanState(false);
        this.readOnly = this._readOnly.asObservable();
        this.#variantContext = new UmbVariantContext(this).inherit();
        this._dataOwner = dataOwner;
        this.#variantId = variantId ?? UmbVariantId.CreateInvariant();
        this.#variantContext.setVariantId(this.#variantId);
        this.#propertyVariantIdPromise = new Promise((resolve) => {
            this.#propertyVariantIdPromiseResolver = resolve;
        });
        this.observe(this._dataOwner.readOnlyGuard.isPermittedForVariant(this.#variantId), (isReadOnly) => {
            this._readOnly.setValue(isReadOnly);
        }, null);
        // TODO: Refactor this into a separate manager/controller of some sort? [NL]
        this.observe(this._dataOwner.structure.contentTypeProperties, (props) => {
            const map = props.map((prop) => ({ alias: prop.alias, variantId: this.#createPropertyVariantId(prop) }));
            this.#propertyVariantIdMap.setValue(map);
            // Resolve promise, to let the once waiting on this know.
            if (this.#propertyVariantIdPromiseResolver) {
                this.#propertyVariantIdPromiseResolver();
                this.#propertyVariantIdPromiseResolver = undefined;
                this.#propertyVariantIdPromise = undefined;
            }
        }, null);
    }
    #createPropertyVariantId(property) {
        return UmbVariantId.Create({
            culture: property.variesByCulture ? this.#variantId.culture : null,
            segment: property.variesBySegment ? this.#variantId.segment : null,
        });
    }
    #propertiesObservable;
    // Should it be possible to get the properties as a list of property aliases?
    get properties() {
        if (!this.#propertiesObservable) {
            this.#propertiesObservable = mergeObservables([this._propertyVariantIdMap, this._dataOwner.values], this.#mergeVariantIdsAndValues);
        }
        return this.#propertiesObservable;
    }
    #mergeVariantIdsAndValues([props, values]) {
        const r = [];
        if (values) {
            for (const prop of props) {
                const f = values.find((v) => prop.alias === v.alias && prop.variantId.compare(v));
                if (f) {
                    r.push(f);
                }
            }
        }
        return r;
    }
    async getProperties() {
        await this.#propertyVariantIdPromise;
        return this.#mergeVariantIdsAndValues([
            this.#propertyVariantIdMap.getValue(),
            this._dataOwner.getValues(),
        ]);
    }
    /**
     * @function propertyVariantId
     * @param {string} propertyAlias - The property alias to observe the variantId of.
     * @returns {Promise<Observable<UmbVariantId | undefined> | undefined>} - The observable for this properties variantId.
     * @description Get an Observable for the variant id of this property.
     */
    async propertyVariantId(propertyAlias) {
        /*
        return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(
            map((property) => (property ? this.#createPropertyVariantId(property) : undefined)),
        );
        */
        return createObservablePart(this._propertyVariantIdMap, (x) => x.find((v) => v.alias === propertyAlias)?.variantId, classEqualMemoization);
    }
    /**
     * @function propertyValueByAlias
     * @param {string} propertyAlias  The alias of the property
     * @returns {Promise<Observable<ReturnType | undefined> | undefined>} - An observable of the property value
     * @description Get an Observable for the value of this property.
     */
    async propertyValueByAlias(propertyAlias) {
        await this._dataOwner.isLoaded();
        await this.#propertyVariantIdPromise;
        return mergeObservables([await this.propertyVariantId(propertyAlias), this._dataOwner.values], ([variantId, values]) => {
            return variantId
                ? values?.find((x) => x?.alias === propertyAlias && variantId.compare(x))?.value
                : undefined;
        });
    }
    // TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy.
    async propertyValueByAliasAndVariantId(propertyAlias, propertyVariantId) {
        return this._dataOwner.propertyValueByAlias(propertyAlias, propertyVariantId);
    }
    /**
     * @function setPropertyValueByVariant
     * @param {string} propertyAlias - The alias of the property
     * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self.
     * @param {UmbVariantId} propertyVariantId - The variant id for the value to be set for.
     * @returns {Promise<unknown>} - A promise that resolves once the value has been set.
     * @description Get the value of this property.
     */
    setPropertyValueByVariant(propertyAlias, value, propertyVariantId) {
        return this._dataOwner.setPropertyValue(propertyAlias, value, propertyVariantId);
    }
    /**
     * @function setPropertyValue
     * @param {string} propertyAlias - The alias for the value to be set
     * @param {PromiseLike<unknown>} value - value can be a promise resolving into the actual value or the raw value it self.
     * @returns {Promise<void>}
     * @description Set the value of this property.
     */
    async setPropertyValue(propertyAlias, value) {
        this._dataOwner.initiatePropertyValueChange();
        await this.#propertyVariantIdPromise;
        const propVariantId = this.#propertyVariantIdMap.getValue().find((x) => x.alias === propertyAlias)?.variantId;
        if (propVariantId) {
            await this._dataOwner.setPropertyValue(propertyAlias, await value, propVariantId);
        }
        this._dataOwner.finishPropertyValueChange();
    }
    destroy() {
        super.destroy();
        this.#propertyVariantIdMap?.destroy();
        this.#propertyVariantIdMap = undefined;
    }
}
