import { jsx as _jsx } from "react/jsx-runtime";
// (C) 2025-2026 GoodData Corporation
import { cloneDeep, set } from "lodash-es";
import { attributeLocalId, bucketAttribute, bucketItems, insightBucket, insightBuckets, insightFilters, insightHasDataDefined, insightLayers, insightTitle, newAttribute, } from "@gooddata/sdk-model";
import { BucketNames, GeoLocationMissingSdkError, VisualizationTypes } from "@gooddata/sdk-ui";
import { GeoChartNextInternal, buildLayerExecution, createPushpinLayer, insightLayersToGeoLayers, isGeoLayerPushpin, } from "@gooddata/sdk-ui-geo/next";
import { createAttributeRef, extractControls, getLatitudeAttribute, getLocationProperties, getPrimaryLayerControls, } from "./geoAttributeHelper.js";
import { buildGeoVisualizationConfig } from "./geoConfigBuilder.js";
import { createConfiguredBuckets, createSortForSegment, distributeMeasures, getLocationItems, sanitizeMeasures, } from "./geoPushpinBucketHelper.js";
import { BUCKETS } from "../../../constants/bucket.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 { limitNumberOfMeasuresInBuckets } from "../../../utils/bucketHelper.js";
import { routeLocalIdRefFiltersToLayers } from "../../../utils/filters/routeLocalIdRefFiltersToLayers.js";
import { removeSort } from "../../../utils/sort.js";
import { setGeoPushpinUiConfig } from "../../../utils/uiConfigHelpers/geoPushpinChartUiConfigHelper.js";
import { GeoPushpinConfigurationPanel } from "../../configurationPanels/GeoPushpinConfigurationPanel.js";
import { PluggableBaseChart } from "../baseChart/PluggableBaseChart.js";
/**
 * Geo pushpin charts support max 2 measures: one for size and one for color
 */
const NUMBER_MEASURES_IN_BUCKETS_LIMIT = 2;
/**
 * PluggableGeoPushpinChartNext
 *
 * Next-generation geo pushpin chart implementation.
 * Supports multi-layer geo charts through the insight layers property.
 * Additional layers configured externally are preserved but not editable in AD.
 *
 * @alpha
 */
export class PluggableGeoPushpinChartNext extends PluggableBaseChart {
    backend;
    workspace;
    constructor(props) {
        super(props);
        this.type = VisualizationTypes.PUSHPIN;
        this.backend = props.backend;
        this.workspace = props.projectId;
        this.initializeProperties(props.visualizationProperties);
    }
    /**
     * Extends reference point with geo pushpin-specific configuration.
     * Configures UI config, removes sorting, enables percent formatting, and updates clustering properties.
     *
     * @param referencePoint - The reference point to extend
     * @returns Promise resolving to the extended reference point
     */
    getExtendedReferencePoint(referencePoint) {
        // IMPORTANT:
        // GeoChartNext is multi-layer, but AD's reference point contains a single buckets model.
        // The generic sanitizeFilters() (in PluggableBaseChart) does not know about additional layers and can
        // incorrectly drop MVFs that are meant for non-root layers. We keep the original filter bucket intact
        // and rely on per-layer execution sanitization instead.
        const originalFilters = cloneDeep(referencePoint.filters);
        return super
            .getExtendedReferencePoint(referencePoint)
            .then((extendedReferencePoint) => {
            let newReferencePoint = setGeoPushpinUiConfig(extendedReferencePoint, this.intl, this.type);
            newReferencePoint = configurePercent(newReferencePoint, true);
            newReferencePoint = removeSort(newReferencePoint);
            const updated = this.updateSupportedProperties(newReferencePoint);
            return { ...updated, filters: originalFilters };
        });
    }
    getUiConfig() {
        const config = cloneDeep(GEO_PUSHPIN_CHART_UICONFIG);
        this.addMetricToFiltersIfEnabled(config);
        return config;
    }
    getSupportedPropertiesList() {
        return GEOPUSHPIN_SUPPORTED_PROPERTIES;
    }
    /**
     * Configures buckets for geo pushpin chart by distributing measures between SIZE and COLOR buckets.
     * Ensures proper measure distribution and applies bucket item limits.
     */
    configureBuckets(extendedReferencePoint) {
        const sanitized = sanitizeMeasures(extendedReferencePoint);
        const buckets = limitNumberOfMeasuresInBuckets(sanitized.buckets, NUMBER_MEASURES_IN_BUCKETS_LIMIT);
        const uiConfig = this.getUiConfig();
        const { sizeMeasures, colorMeasures } = distributeMeasures(buckets, uiConfig);
        const configuredBuckets = createConfiguredBuckets(buckets, sizeMeasures, colorMeasures, uiConfig);
        set(sanitized, BUCKETS, configuredBuckets);
        return sanitized;
    }
    /**
     * Updates properties in the reference point including clustering and location-based properties.
     */
    updateSupportedProperties(referencePoint) {
        const buckets = referencePoint?.buckets ?? [];
        const uiConfig = this.getUiConfig();
        const locationItem = getLocationItems(buckets, uiConfig)[0];
        if (!locationItem) {
            return referencePoint;
        }
        const referencePointConfigured = cloneDeep(referencePoint);
        const visualizationProperties = this.visualizationProperties || {};
        const { controls = {} } = visualizationProperties;
        const locationProperties = getLocationProperties(locationItem);
        // Don't add groupNearbyPoints if it wasn't already in the properties.
        // This prevents marking the insight as "dirty" when just opening it.
        // The rendering code handles the default value (true when clustering is allowed).
        set(referencePointConfigured, "properties", {
            controls: {
                ...controls,
                ...locationProperties,
            },
        });
        if (this.references) {
            set(referencePointConfigured, "references", this.references);
        }
        return referencePointConfigured;
    }
    /**
     * Validates insight has data defined before rendering.
     * Throws EmptyAfmSdkError if no data is defined.
     *
     * @param insight - The insight definition to validate
     * @returns true if validation passes
     * @throws EmptyAfmSdkError if no data is defined
     * @throws GeoLocationMissingSdkError if latitude attribute is not defined
     */
    checkBeforeRender(insight) {
        if (!getLatitudeAttribute(insight)) {
            throw new GeoLocationMissingSdkError();
        }
        if (!insightHasDataDefined(insight)) {
            throw new EmptyAfmSdkError();
        }
        return true;
    }
    /**
     * Creates execution for geo pushpin chart with virtual buckets for latitude/longitude.
     * For backends supporting separate lat/long labels, creates virtual LATITUDE and LONGITUDE buckets
     * and excludes the original LOCATION bucket to avoid duplicate attributes.
     *
     * @param options - Visualization options
     * @param insight - Insight definition containing buckets and filters
     * @param executionFactory - Factory for creating executions
     * @returns Configured execution with proper dimensions and sorting
     */
    getExecution(options, insight, executionFactory) {
        const { primaryLayer, config, filters } = this.buildPrimaryLayerContext(options, insight);
        const { globalFilters, routedByLayerId } = routeLocalIdRefFiltersToLayers(filters, [
            { id: primaryLayer.id, buckets: insightBuckets(insight) },
            ...insightLayers(insight).map((l) => ({ id: l.id, buckets: l.buckets })),
        ]);
        const effectiveFilters = [...globalFilters, ...(routedByLayerId.get(primaryLayer.id) ?? [])];
        return buildLayerExecution(primaryLayer, {
            backend: this.backend,
            workspace: this.workspace,
            config,
            execConfig: options.executionConfig,
            globalFilters: effectiveFilters,
            executionFactory,
        });
    }
    getExecutions(options, insight, executionFactory) {
        const { primaryLayer, config, filters } = this.buildPrimaryLayerContext(options, insight);
        const { globalFilters, routedByLayerId } = routeLocalIdRefFiltersToLayers(filters, [
            { id: primaryLayer.id, buckets: insightBuckets(insight) },
            ...insightLayers(insight).map((l) => ({ id: l.id, buckets: l.buckets })),
        ]);
        const insightLayerDefs = insightLayers(insight);
        const additionalLayers = insightLayersToGeoLayers(insightLayerDefs);
        const resolvedAdditionalLayers = additionalLayers.filter((layer) => !this.shouldSkipLayer(layer));
        return this.buildAdditionalLayerExecutions(resolvedAdditionalLayers, options, config, globalFilters, routedByLayerId, executionFactory);
    }
    renderConfigurationPanel(insight, options) {
        const configPanelElement = this.getConfigPanelElement();
        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, configurationPanelRenderers: options.custom?.configurationPanelRenderers }), configPanelElement);
        }
    }
    /**
     * Builds geo-specific visualization configuration.
     * Extends base chart config with geo-specific properties like map tiles and tooltips.
     *
     * @param options - Visualization options
     * @param supportedControls - Supported visualization properties
     * @returns Next-gen geo chart configuration
     */
    buildVisualizationConfig(options, supportedControls) {
        const { colorMapping } = super.buildVisualizationConfig(options, supportedControls);
        return buildGeoVisualizationConfig({
            options,
            supportedControls,
            colorMapping,
            environment: this.environment,
        });
    }
    renderVisualization(options, insight, executionFactory) {
        const { custom = {}, locale, theme } = options;
        const { drillableItems } = custom;
        const { config: configWithResolvedTooltip } = this.buildPrimaryLayerContext(options, insight);
        const primaryExecution = this.getExecution(options, insight, executionFactory);
        const additionalLayerExecutions = this.getExecutions(options, insight, executionFactory) ?? [];
        const geoChartProps = {
            backend: this.backend,
            workspace: this.workspace,
            type: "pushpin",
            execution: primaryExecution,
            executions: additionalLayerExecutions,
            execConfig: options.executionConfig,
            drillableItems,
            config: configWithResolvedTooltip,
            locale,
            theme,
            pushData: this.handlePushData,
            afterRender: this.afterRender,
            onError: this.onError,
            onExportReady: this.onExportReady,
            onLoadingChanged: this.onLoadingChanged,
            onDrill: this.onDrill,
        };
        this.renderFun(_jsx(GeoChartNextInternal, { ...geoChartProps }), this.getElement());
    }
    buildPrimaryLayerContext(options, insight) {
        const supportedControls = this.visualizationProperties.controls || {};
        const fullConfig = this.buildVisualizationConfig(options, supportedControls);
        const controlsWithFallback = {
            ...this.getInsightControlsWithFallback(insight),
            ...supportedControls,
        };
        const filters = insightFilters(insight);
        const sortBy = createSortForSegment(insight);
        const locationBucket = insightBucket(insight, BucketNames.LOCATION);
        const sizeBucket = insightBucket(insight, BucketNames.SIZE);
        const colorBucket = insightBucket(insight, BucketNames.COLOR);
        const segmentBucket = insightBucket(insight, BucketNames.SEGMENT);
        const latitudeFromBucket = locationBucket ? bucketAttribute(locationBucket) : undefined;
        const size = sizeBucket ? bucketItems(sizeBucket)[0] : undefined;
        const color = colorBucket ? bucketItems(colorBucket)[0] : undefined;
        const segmentBy = segmentBucket ? bucketAttribute(segmentBucket) : undefined;
        const tooltipTextId = controlsWithFallback?.["tooltipText"];
        const latitudeId = controlsWithFallback?.["latitude"];
        const longitudeId = controlsWithFallback?.["longitude"];
        const tooltipTextAttribute = this.createAttributeFromId(latitudeFromBucket, tooltipTextId, "tooltipText_df");
        // IMPORTANT: keep localId of the original LOCATION attribute so MVF/ranking filters that reference it
        // (via localIdRef in dimensionality) do not become dangling when LOCATION bucket is replaced by LAT/LNG.
        const latitudeLocalId = latitudeFromBucket ? attributeLocalId(latitudeFromBucket) : "latitude_df";
        const latitudeAttribute = this.createAttributeFromId(latitudeFromBucket, latitudeId, latitudeLocalId);
        const longitudeAttribute = this.createAttributeFromId(latitudeFromBucket, longitudeId, "longitude_df");
        if (!latitudeAttribute || !longitudeAttribute) {
            throw new Error("Pushpin layer requires latitude and longitude attributes");
        }
        const configWithResolvedTooltip = {
            ...fullConfig,
            tooltipText: tooltipTextAttribute ?? undefined,
        };
        // Set primary layer name to the insight title so legend/title logic is consistent for all layers.
        const title = insightTitle(insight);
        const primaryLayer = createPushpinLayer({
            ...(title ? { name: title } : {}),
            latitude: latitudeAttribute,
            longitude: longitudeAttribute,
            sortBy,
            ...(size ? { size } : {}),
            ...(color ? { color } : {}),
            ...(segmentBy ? { segmentBy } : {}),
        });
        return {
            primaryLayer,
            config: configWithResolvedTooltip,
            filters,
        };
    }
    buildAdditionalLayerExecutions(layers, options, config, globalFilters, routedByLayerId, executionFactory) {
        if (!layers.length) {
            return [];
        }
        return layers.map((layer) => {
            const effectiveFilters = [...globalFilters, ...(routedByLayerId.get(layer.id) ?? [])];
            return buildLayerExecution(layer, {
                backend: this.backend,
                workspace: this.workspace,
                config,
                execConfig: options.executionConfig,
                globalFilters: effectiveFilters,
                executionFactory,
            });
        });
    }
    createAttributeFromId(baseAttribute, attributeId, localId) {
        if (!baseAttribute || !attributeId) {
            return undefined;
        }
        const ref = createAttributeRef(baseAttribute, attributeId);
        return newAttribute(ref, (attribute) => attribute.localId(localId));
    }
    shouldSkipLayer(layer) {
        if (!isGeoLayerPushpin(layer)) {
            return false;
        }
        // Skip layers when latitude or longitude is missing
        return !layer.latitude || !layer.longitude;
    }
    getInsightControlsWithFallback(insight) {
        const insightControls = extractControls(insight) || {};
        const layerControls = getPrimaryLayerControls(insight);
        return {
            ...layerControls,
            ...insightControls,
        };
    }
}
//# sourceMappingURL=PluggableGeoPushpinChartNext.js.map