import { jsx as _jsx } from "react/jsx-runtime";
// (C) 2025-2026 GoodData Corporation
import { cloneDeep, set } from "lodash-es";
import { insightBuckets, insightFilters, insightHasDataDefined, insightLayers, insightSorts, insightTitle, } from "@gooddata/sdk-model";
import { GeoLocationMissingSdkError, VisualizationTypes } from "@gooddata/sdk-ui";
import { isGeoLayerPushpin } from "@gooddata/sdk-ui-geo";
import { GeoChartInternal, PUSHPIN_LAYER_ID, buildLayerExecution, insightLayerToGeoLayer, insightLayersToGeoLayers, } from "@gooddata/sdk-ui-geo/internal";
import { extractControls, getLatitudeAttribute, getLocationProperties, getPrimaryLayerControls, } from "./geoAttributeHelper.js";
import { buildGeoVisualizationConfig } from "./geoConfigBuilder.js";
import { createConfiguredBuckets, distributeMeasures, getLocationItems, sanitizeMeasures, } from "./geoPushpinBucketHelper.js";
import { BUCKETS } from "../../../constants/bucket.js";
import { isGeoChartsViewportConfigEnabled } from "../../../constants/featureFlags.js";
import { GEOPUSHPIN_NEXT_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 { sanitizeGeoReferencePointFilters } from "../../../utils/filters/sanitizeGeoReferencePointFilters.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";
import { getGeoControlsWithFallback, getGeoVisualizationPropertiesWithFallback, } from "../geoCommon/geoVisualizationPropertiesWithFallback.js";
import { LiveMapViewTracker, createSyncedViewportHandlers } from "../geoCommon/liveMapViewTracking.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;
    liveMapView = new LiveMapViewTracker();
    constructor(props) {
        super(props);
        this.type = VisualizationTypes.PUSHPIN;
        this.backend = props.backend;
        this.workspace = props.projectId;
        this.initializeProperties(props.visualizationProperties);
        this.initializePropertiesMeta();
    }
    // Clear stale propertiesMeta synchronously. useGeoPushData will push the
    // computed value once async data loading completes.
    initializePropertiesMeta() {
        this.pushData({
            propertiesMeta: { isGeoSegmentConflictRecommended: false },
        });
    }
    /**
     * 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);
            const enableImprovedAdFilters = this.featureFlags?.enableImprovedAdFilters ?? true;
            const sanitizedFilters = sanitizeGeoReferencePointFilters(originalFilters, referencePoint.buckets, updated.buckets, enableImprovedAdFilters);
            return { ...updated, filters: sanitizedFilters };
        });
    }
    getUiConfig() {
        const config = cloneDeep(GEO_PUSHPIN_CHART_UICONFIG);
        this.addMetricToFiltersIfEnabled(config);
        return config;
    }
    getSupportedPropertiesList() {
        return GEOPUSHPIN_NEXT_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 (!insightHasDataDefined(insight)) {
            throw new EmptyAfmSdkError();
        }
        const latitudeFromBucket = getLatitudeAttribute(insight);
        if (!latitudeFromBucket) {
            throw new GeoLocationMissingSdkError();
        }
        const controlsWithFallback = getGeoControlsWithFallback(this.visualizationProperties, this.getInsightControlsWithFallback(insight));
        const latitudeCandidate = controlsWithFallback?.["latitude"];
        const longitudeCandidate = controlsWithFallback?.["longitude"];
        const latitudeId = typeof latitudeCandidate === "string" ? latitudeCandidate : undefined;
        const longitudeId = typeof longitudeCandidate === "string" ? longitudeCandidate : undefined;
        if (!latitudeId || !longitudeId) {
            throw new GeoLocationMissingSdkError();
        }
        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 ctx = this.buildPrimaryLayerContext(options, insight);
        if (!ctx) {
            // Never throw outside checkBeforeRender(). This fallback execution should be unreachable
            // when called from update()-driven rendering (validation happens in checkBeforeRender).
            return executionFactory.forInsight(insight).withExecConfig(options.executionConfig);
        }
        const { primaryLayer, config, filters } = ctx;
        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 ctx = this.buildPrimaryLayerContext(options, insight);
        if (!ctx) {
            return [];
        }
        const { primaryLayer, config, filters } = ctx;
        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();
        const isViewportConfigEnabled = isGeoChartsViewportConfigEnabled(this.featureFlags);
        this.liveMapView.resetIfInsightChanged(insight);
        if (configPanelElement) {
            this.renderFun(_jsx(GeoPushpinConfigurationPanel, { locale: this.locale, pushData: this.pushData, properties: getGeoVisualizationPropertiesWithFallback(this.visualizationProperties, this.getInsightControlsWithFallback(insight)), 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, getCurrentMapView: isViewportConfigEnabled ? this.getCurrentMapView : undefined }), 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
     */
    buildGeoVisualizationConfig(options, supportedControls) {
        const { colorMapping } = super.buildVisualizationConfig(options, supportedControls);
        return buildGeoVisualizationConfig({
            options,
            supportedControls,
            colorMapping,
            environment: this.environment,
            featureFlags: this.featureFlags,
        });
    }
    renderVisualization(options, insight, executionFactory) {
        const { custom = {}, locale, theme } = options;
        const { drillableItems } = custom;
        const ctx = this.buildPrimaryLayerContext(options, insight);
        if (!ctx) {
            return;
        }
        this.liveMapView.resetIfInsightChanged(insight);
        const isViewportConfigEnabled = isGeoChartsViewportConfigEnabled(this.featureFlags);
        const { config: configWithResolvedTooltip } = ctx;
        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,
            onCenterPositionChanged: isViewportConfigEnabled ? this.handleCenterPositionChanged : undefined,
            onZoomChanged: isViewportConfigEnabled ? this.handleZoomChanged : undefined,
        };
        this.renderFun(_jsx(GeoChartInternal, { ...geoChartProps }), this.getElement());
    }
    buildPrimaryLayerContext(options, insight) {
        const controlsWithFallback = getGeoControlsWithFallback(this.visualizationProperties, this.getInsightControlsWithFallback(insight));
        const fullConfig = this.buildGeoVisualizationConfig(options, controlsWithFallback);
        const filters = insightFilters(insight);
        const sortBy = insightSorts(insight);
        const title = insightTitle(insight);
        const controlsForPrimaryLayer = {
            ...controlsWithFallback,
            ...(fullConfig.colorPalette ? { colorPalette: fullConfig.colorPalette } : {}),
        };
        const primaryLayer = insightLayerToGeoLayer({
            id: PUSHPIN_LAYER_ID,
            type: "pushpin",
            buckets: insightBuckets(insight),
            sorts: sortBy,
            properties: { controls: controlsForPrimaryLayer },
            ...(title ? { name: title } : {}),
        });
        if (!primaryLayer || !isGeoLayerPushpin(primaryLayer)) {
            // Invalid or incomplete geo config; validation is handled in checkBeforeRender().
            return undefined;
        }
        return {
            primaryLayer,
            // Prevent treating chart-level colors as global. They are stored on the primary layer above.
            config: { ...fullConfig, colorPalette: undefined, colorMapping: undefined },
            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,
            });
        });
    }
    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,
        };
    }
    syncedHandlers = createSyncedViewportHandlers(this.liveMapView, {
        getEnvironment: () => this.environment,
        getVisualizationProperties: () => this.visualizationProperties,
        setVisualizationProperties: (props) => {
            this.visualizationProperties = props;
        },
        pushData: (data) => this.pushData(data),
    });
    handleCenterPositionChanged = this.syncedHandlers.handleCenterPositionChanged;
    handleZoomChanged = this.syncedHandlers.handleZoomChanged;
    getCurrentMapView = () => this.liveMapView.getCurrentMapView(this.visualizationProperties);
}
//# sourceMappingURL=PluggableGeoPushpinChartNext.js.map