import { jsx as _jsx } from "react/jsx-runtime";
// (C) 2019-2026 GoodData Corporation
import { cloneDeep, isEmpty, set } from "lodash-es";
import { attributeAlias, attributeDisplayFormRef, attributeLocalId, bucketAttribute, idRef, insightBucket, insightBuckets, insightFilters, insightHasDataDefined, isUriRef, newAttribute, newAttributeSort, newBucket, uriRef, } from "@gooddata/sdk-model";
import { BucketNames, VisualizationTypes } from "@gooddata/sdk-ui";
import { CoreGeoChart, getGeoChartDimensions, } from "@gooddata/sdk-ui-geo";
import { ATTRIBUTE, BUCKETS, METRIC } from "../../../constants/bucket.js";
import { ANALYTICAL_ENVIRONMENT, DASHBOARDS_ENVIRONMENT } from "../../../constants/properties.js";
import { GEOPUSHPIN_SUPPORTED_PROPERTIES } from "../../../constants/supportedProperties.js";
import { GEO_PUSHPIN_CHART_UICONFIG } from "../../../constants/uiConfig.js";
import { EmptyAfmSdkError, } from "../../../interfaces/Visualization.js";
import { configurePercent } from "../../../utils/bucketConfig.js";
import { getAllMeasures, getAttributeItemsWithoutStacks, getItemsCount, getItemsFromBuckets, getPreferredBucketItems, isDateBucketItem, limitNumberOfMeasuresInBuckets, removeAllArithmeticMeasuresFromDerived, removeAllDerivedMeasures, removeShowOnSecondaryAxis, } from "../../../utils/bucketHelper.js";
import { removeSort } from "../../../utils/sort.js";
import { setGeoPushpinUiConfig, updateConfigWithSettings, } from "../../../utils/uiConfigHelpers/geoPushpinChartUiConfigHelper.js";
import { GeoPushpinConfigurationPanel } from "../../configurationPanels/GeoPushpinConfigurationPanel.js";
import { PluggableBaseChart } from "../baseChart/PluggableBaseChart.js";
const NUMBER_MEASURES_IN_BUCKETS_LIMIT = 2;
/**
 * PluggableGeoPushpinChart
 *
 * ## Buckets
 *
 * | Name        | Id          | Accepts                                                       |
 * |-------------|-------------|---------------------------------------------------------------|
 * | Location    | location    | geo attributes only                                           |
 * | Latitude    | latitude    | geo attributes only, added internally, not accessible from UI |
 * | Longitude   | longitude   | geo attributes only, added internally, not accessible from UI |
 * | Size        | size        | measures only                                                 |
 * | Color       | color       | measures only                                                 |
 * | Segment     | segment     | attributes only                                               |
 * | TooltipText | tooltipText | attributes only, added internally, not accessible from UI     |
 *
 * Internal buckets are used only for execution, they never exist in reference point.
 * In ref. point they are represented by items in properties
 *
 * ### Bucket axioms
 *
 * - |Location| = 1
 * - |Size| ≤ 1
 * - |Color| ≤ 1
 * - |Segment| ≤ 1
 *
 * ## Dimensions
 *
 * The PluggableGeoPushpinChart creates either one- or two-dimensional execution.
 *
 * In the case when latitude and longitude is in the one string label, delimited by ";":
 * - |Size| + |Color| ≥ 1 ⇒ [[MeasureGroupIdentifier], [Location, Segment, TooltipText]]
 * - |Size| + |Color| = 0 ⇒ [[Location, Segment, TooltipText]]
 *
 * In the case when latitude and longitude is in two numerical separate labels:
 * - |Size| + |Color| ≥ 1 ⇒ [[MeasureGroupIdentifier], [Latitude, Longitude, Segment, TooltipText]]
 * - |Size| + |Color| = 0 ⇒ [[Latitude, Longitude, Segment, TooltipText]]
 *
 * ## Sorts
 *
 * Unless the user specifies otherwise, the sorts used by default are:
 *
 * - |Segment| ≥ 1 ⇒ [attributeSort(Segment[0])]
 */
export class PluggableGeoPushpinChart extends PluggableBaseChart {
    constructor(props) {
        super(props);
        this.type = VisualizationTypes.PUSHPIN;
        this.initializeProperties(props.visualizationProperties);
    }
    checkBeforeRender(insight) {
        if (!insightHasDataDefined(insight)) {
            throw new EmptyAfmSdkError();
        }
        return true;
    }
    getExtendedReferencePoint(referencePoint) {
        return super
            .getExtendedReferencePoint(referencePoint)
            .then((extendedReferencePoint) => {
            let newReferencePoint = setGeoPushpinUiConfig(extendedReferencePoint, this.intl, this.type);
            newReferencePoint = configurePercent(newReferencePoint, true);
            newReferencePoint = removeSort(newReferencePoint);
            return this.updateSupportedProperties(newReferencePoint);
        });
    }
    getUiConfig() {
        const config = cloneDeep(GEO_PUSHPIN_CHART_UICONFIG);
        this.addMetricToFiltersIfEnabled(config);
        return config;
    }
    getExecution(options, insight, executionFactory) {
        const { executionConfig } = options;
        const buckets = this.prepareBuckets(insight);
        return executionFactory
            .forBuckets(buckets, insightFilters(insight))
            .withDimensions(getGeoChartDimensions)
            .withSorting(...this.createSort(insight))
            .withExecConfig(executionConfig);
    }
    getSupportedPropertiesList() {
        return GEOPUSHPIN_SUPPORTED_PROPERTIES;
    }
    configureBuckets(extendedReferencePoint) {
        const newExtendedReferencePoint = this.sanitizeMeasures(extendedReferencePoint);
        const buckets = limitNumberOfMeasuresInBuckets(newExtendedReferencePoint.buckets, NUMBER_MEASURES_IN_BUCKETS_LIMIT);
        const allMeasures = getAllMeasures(buckets);
        const primaryMeasures = getPreferredBucketItems(buckets, [BucketNames.MEASURES, BucketNames.SIZE], [METRIC]);
        const secondaryMeasures = getPreferredBucketItems(buckets, [BucketNames.SECONDARY_MEASURES, BucketNames.COLOR], [METRIC]);
        const sizeMeasures = (primaryMeasures.length > 0
            ? primaryMeasures
            : allMeasures.filter((measure) => !secondaryMeasures.includes(measure))).slice(0, this.getPreferredBucketItemLimit(BucketNames.SIZE));
        const colorMeasures = (secondaryMeasures.length > 0
            ? secondaryMeasures
            : allMeasures.filter((measure) => !sizeMeasures.includes(measure))).slice(0, this.getPreferredBucketItemLimit(BucketNames.COLOR));
        set(newExtendedReferencePoint, BUCKETS, [
            {
                localIdentifier: BucketNames.LOCATION,
                items: this.getLocationItems(buckets),
            },
            {
                localIdentifier: BucketNames.SIZE,
                items: removeShowOnSecondaryAxis(sizeMeasures),
            },
            {
                localIdentifier: BucketNames.COLOR,
                items: removeShowOnSecondaryAxis(colorMeasures),
            },
            {
                localIdentifier: BucketNames.SEGMENT,
                items: this.getSegmentItems(buckets),
            },
        ]);
        return newExtendedReferencePoint;
    }
    renderConfigurationPanel(insight, options) {
        const configPanelElement = this.getConfigPanelElement();
        // NOTE: using pushData directly; no handlePushData here as in other visualizations.
        if (configPanelElement) {
            this.renderFun(_jsx(GeoPushpinConfigurationPanel, { locale: this.locale, pushData: this.pushData, properties: this.visualizationProperties, references: this.references, propertiesMeta: this.propertiesMeta, insight: insight, colors: this.colors, type: this.type, isError: this.getIsError(), isLoading: this.isLoading, featureFlags: this.featureFlags, permissions: this.permissions, configurationPanelRenderers: options.custom?.configurationPanelRenderers }), configPanelElement);
        }
    }
    buildVisualizationConfig(options, supportedControls) {
        const { config = {}, customVisualizationConfig = {} } = options;
        const { center, legend, viewport = {} } = supportedControls;
        const { colorMapping } = super.buildVisualizationConfig(options, supportedControls);
        const centerProp = center ? { center } : {};
        const legendProp = legend ? { legend } : {};
        const { isInEditMode, isExportMode } = config;
        if (this.environment === DASHBOARDS_ENVIRONMENT) {
            set(supportedControls, "legend.responsive", "autoPositionWithPopup");
        }
        const viewportProp = {
            viewport: {
                ...viewport,
                frozen: isInEditMode || isExportMode,
            },
        };
        const geoChartConfig = {
            ...config,
            ...centerProp,
            ...legendProp,
            ...viewportProp,
        };
        const isKDInViewMode = this.environment !== ANALYTICAL_ENVIRONMENT && !isInEditMode;
        const cooperativeGestures = customVisualizationConfig?.cooperativeGestures === undefined
            ? isKDInViewMode
            : customVisualizationConfig.cooperativeGestures;
        return {
            separators: config.separators,
            colorPalette: config.colorPalette,
            mapboxToken: config.mapboxToken,
            ...supportedControls,
            ...geoChartConfig,
            colorMapping,
            ...customVisualizationConfig,
            cooperativeGestures,
        };
    }
    renderVisualization(options, insight, executionFactory) {
        const { dimensions = { height: undefined }, custom = {}, locale, theme } = options;
        const { height } = dimensions;
        const { intl } = this;
        // keep height undef for AD; causes indigo-visualizations to pick default 100%
        const resultingHeight = this.environment === DASHBOARDS_ENVIRONMENT ? height : undefined;
        const { drillableItems } = custom;
        const supportedControls = this.visualizationProperties.controls || {};
        const fullConfig = this.buildVisualizationConfig(options, supportedControls);
        const execution = this.getExecution(options, insight, executionFactory);
        const geoPushpinProps = {
            drillableItems,
            config: updateConfigWithSettings(fullConfig, this.featureFlags),
            height: resultingHeight,
            intl,
            locale,
            execution,
            pushData: this.handlePushData,
            afterRender: this.afterRender,
            onDrill: this.onDrill,
            onError: this.onError,
            onExportReady: this.onExportReady,
            onLoadingChanged: this.onLoadingChanged,
            onDataView: this.onDataView,
            LoadingComponent: undefined,
            ErrorComponent: undefined,
            theme: theme,
            enableExecutionCancelling: fullConfig.enableExecutionCancelling ?? false,
        };
        this.renderFun(_jsx(CoreGeoChart, { ...geoPushpinProps }), this.getElement());
    }
    withEmptyAttributeTargets(data) {
        return {
            ...data.availableDrillTargets,
            attributes: [],
        };
    }
    // This is effectively calling super.handlePushData()
    // https://stackoverflow.com/questions/31088947/inheritance-method-call-triggers-typescript-compiler-error
    // https://github.com/basarat/typescript-book/blob/master/docs/arrow-functions.md#tip-arrow-functions-and-inheritance
    // with new TS we got Property 'handlePushData' is used before its initialization.ts(2729)
    // it is not possible to call super.handlePushData() directly and get reference to this.handlePushData in constructor
    // tested in runtime and it works
    // oxlint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    superHandlePushData = this.handlePushData;
    handlePushData = (data) => {
        // For pushpin chart we do not support drilling from attributes.
        this.superHandlePushData({
            ...data,
            ...(data.availableDrillTargets && {
                availableDrillTargets: this.withEmptyAttributeTargets(data),
            }),
        });
    };
    sanitizeMeasures(extendedReferencePoint) {
        const newExtendedReferencePoint = removeAllArithmeticMeasuresFromDerived(extendedReferencePoint);
        return removeAllDerivedMeasures(newExtendedReferencePoint);
    }
    createSort(insight) {
        const bucket = insightBucket(insight, BucketNames.SEGMENT);
        const segmentAttribute = bucket && bucketAttribute(bucket);
        // sort by second attribute (1st: location, 2nd: segmentBy, 3rd: tooltipText)
        if (segmentAttribute) {
            return [newAttributeSort(segmentAttribute, "asc")];
        }
        return [];
    }
    getSegmentItems(buckets) {
        let segments = getPreferredBucketItems(buckets, [BucketNames.STACK, BucketNames.SEGMENT, BucketNames.COLUMNS], [ATTRIBUTE]);
        const nonSegmentAttributes = getAttributeItemsWithoutStacks(buckets);
        if (nonSegmentAttributes.length > 1 && isEmpty(segments)) {
            const locationItems = this.getLocationItems(buckets);
            segments = nonSegmentAttributes
                .filter((attribute) => !locationItems.includes(attribute))
                .filter((attribute) => !isDateBucketItem(attribute))
                .slice(0, 1);
        }
        return segments.slice(0, this.getPreferredBucketItemLimit(BucketNames.SEGMENT));
    }
    getLocationItems(buckets) {
        const locationItems = getItemsFromBuckets(buckets, [BucketNames.ATTRIBUTE, BucketNames.VIEW, BucketNames.LOCATION, BucketNames.TREND], [ATTRIBUTE]).filter((bucketItem) => Boolean(bucketItem.locationDisplayFormRef));
        return locationItems.slice(0, this.getPreferredBucketItemLimit(BucketNames.LOCATION));
    }
    getPreferredBucketItemLimit(preferredBucket) {
        const { buckets: bucketsUiConfig } = this.getUiConfig();
        return bucketsUiConfig[preferredBucket].itemsLimit;
    }
    updateSupportedProperties(referencePoint) {
        const buckets = referencePoint?.buckets ?? [];
        const locationItem = this.getLocationItems(buckets)[0];
        if (!locationItem) {
            return referencePoint;
        }
        const referencePointConfigured = cloneDeep(referencePoint);
        const visualizationProperties = this.visualizationProperties || {};
        const { controls = {} } = visualizationProperties;
        const hasSizeMeasure = getItemsCount(buckets, BucketNames.SIZE) > 0;
        const hasColorMeasure = getItemsCount(buckets, BucketNames.COLOR) > 0;
        const hasLocationAttribute = getItemsCount(buckets, BucketNames.LOCATION) > 0;
        const hasSegmentAttribute = getItemsCount(buckets, BucketNames.SEGMENT) > 0;
        const groupNearbyPoints = hasLocationAttribute && !hasColorMeasure && !hasSizeMeasure && !hasSegmentAttribute;
        const locationProperties = this.getLocationProperties(locationItem);
        set(referencePointConfigured, "properties", {
            controls: {
                points: {
                    groupNearbyPoints,
                },
                ...controls,
                ...locationProperties,
            },
        });
        if (this.references) {
            set(referencePointConfigured, "references", this.references);
        }
        return referencePointConfigured;
    }
    getLocationProperties(locationItem) {
        if (this.backendCapabilities.supportsSeparateLatitudeLongitudeLabels) {
            const latitudeDfRef = locationItem.displayForms?.find((displayForm) => displayForm.type === "GDC.geo.pin_latitude")?.ref;
            const longitudeDfRef = locationItem.displayForms?.find((displayForm) => displayForm.type === "GDC.geo.pin_longitude")?.ref;
            const latitude = isUriRef(latitudeDfRef) ? latitudeDfRef?.uri : latitudeDfRef?.identifier;
            const longitude = isUriRef(longitudeDfRef) ? longitudeDfRef?.uri : longitudeDfRef?.identifier;
            return {
                latitude,
                longitude,
            };
        }
        return {};
    }
    prepareBuckets(insight) {
        const supportedControls = this.visualizationProperties?.controls || {};
        // we need to shallow copy the buckets so that we can add more without mutating the original array
        const buckets = [...insightBuckets(insight)];
        if (!this.backendCapabilities.supportsSeparateLatitudeLongitudeLabels) {
            return buckets;
        }
        if (supportedControls?.["latitude"]) {
            const latitude = supportedControls?.["latitude"];
            const bucket = this.createVirtualBucketFromLocationAttribute(insight, BucketNames.LATITUDE, latitude
            // reuse local identifier of original item to keep references working, eg. ranking filter
            );
            if (bucket) {
                buckets.push(bucket);
            }
        }
        if (supportedControls?.["longitude"]) {
            const longitude = supportedControls?.["longitude"];
            const bucket = this.createVirtualBucketFromLocationAttribute(insight, BucketNames.LONGITUDE, longitude, "longitude_df");
            if (bucket) {
                buckets.push(bucket);
            }
        }
        // do not include original LOCATION bucket (latitude would be duplicated and two executions
        // would be made because of how local IDs of attributes are normalized)
        return buckets.filter((bucket) => bucket.localIdentifier !== BucketNames.LOCATION);
    }
    /**
     * Creates new virtual bucket from existing LOCATION bucket
     * @param insight - current insight
     * @param bucketName - new bucket name
     * @param attributeId - id of bucket item
     * @param attributeLocalIdentifier - local identifier of bucket item, Location item one will be used if not defined
     */
    createVirtualBucketFromLocationAttribute(insight, bucketName, attributeId, attributeLocalIdentifier) {
        const locationBucket = insightBucket(insight, BucketNames.LOCATION);
        if (locationBucket) {
            const attribute = bucketAttribute(locationBucket);
            if (attribute) {
                let ref = idRef(attributeId, "displayForm");
                const alias = attributeAlias(attribute);
                const localIdentifier = attributeLocalId(attribute);
                if (isUriRef(attributeDisplayFormRef(attribute))) {
                    ref = uriRef(attributeId);
                }
                const existingVirtualBucket = insightBucket(insight, bucketName);
                if (!existingVirtualBucket) {
                    return newBucket(bucketName, newAttribute(ref, (m) => m.localId(attributeLocalIdentifier ?? localIdentifier).alias(alias)));
                }
            }
        }
        return undefined;
    }
}
//# sourceMappingURL=PluggableGeoPushpinChart.js.map