import { jsx as _jsx } from "react/jsx-runtime";
// (C) 2025-2026 GoodData Corporation
import { cloneDeep, set } from "lodash-es";
import { insightBuckets, insightFilters, insightHasDataDefined, insightLayers, insightTitle, } from "@gooddata/sdk-model";
import { GeoAreaMissingSdkError, VisualizationTypes } from "@gooddata/sdk-ui";
import { isGeoLayerPushpin } from "@gooddata/sdk-ui-geo";
import { AREA_LAYER_ID, GeoChartInternal, buildLayerExecution, insightLayerToGeoLayer, insightLayersToGeoLayers, } from "@gooddata/sdk-ui-geo/internal";
import { createAreaConfiguredBuckets, createAreaSortForSegment, getAreaItems, getColorMeasures, getSegmentItems, hasAreaMinimumData, sanitizeAreaMeasures, } from "./geoAreaBucketHelper.js";
import { buildAreaVisualizationConfig } from "./geoAreaConfigBuilder.js";
import { BUCKETS } from "../../../constants/bucket.js";
import { isGeoChartsViewportConfigEnabled } from "../../../constants/featureFlags.js";
import { GEOAREA_SUPPORTED_PROPERTIES } from "../../../constants/supportedProperties.js";
import { GEO_AREA_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 { setGeoAreaUiConfig } from "../../../utils/uiConfigHelpers/geoAreaChartUiConfigHelper.js";
import { GeoAreaConfigurationPanel } from "../../configurationPanels/GeoAreaConfigurationPanel.js";
import { PluggableBaseChart } from "../baseChart/PluggableBaseChart.js";
import { extractControls } from "../geoChartNext/geoAttributeHelper.js";
import { getGeoControlsWithFallback, getGeoVisualizationPropertiesWithFallback, } from "../geoCommon/geoVisualizationPropertiesWithFallback.js";
import { LiveMapViewTracker, createSyncedViewportHandlers } from "../geoCommon/liveMapViewTracking.js";
/**
 * Geo area charts support max 1 measure for color
 */
const NUMBER_MEASURES_IN_BUCKETS_LIMIT = 1;
/**
 * PluggableGeoAreaChart
 *
 * Next-generation geo area chart implementation
 *
 * @alpha
 */
export class PluggableGeoAreaChart extends PluggableBaseChart {
    backend;
    workspace;
    liveMapView = new LiveMapViewTracker();
    constructor(props) {
        super(props);
        this.type = VisualizationTypes.CHOROPLETH;
        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 area-specific configuration.
     */
    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 (e.g., pushpin layer MVFs).
        // We keep the original filter bucket intact here and rely on per-layer execution sanitization instead.
        const originalFilters = cloneDeep(referencePoint.filters);
        return super
            .getExtendedReferencePoint(referencePoint)
            .then((extendedReferencePoint) => {
            let newReferencePoint = setGeoAreaUiConfig(extendedReferencePoint, this.intl);
            newReferencePoint = configurePercent(newReferencePoint, true);
            newReferencePoint = removeSort(newReferencePoint);
            const enableImprovedAdFilters = this.featureFlags?.enableImprovedAdFilters ?? true;
            const sanitizedFilters = sanitizeGeoReferencePointFilters(originalFilters, referencePoint.buckets, newReferencePoint.buckets, enableImprovedAdFilters);
            return { ...newReferencePoint, filters: sanitizedFilters };
        });
    }
    getUiConfig() {
        return cloneDeep(GEO_AREA_CHART_UICONFIG);
    }
    getSupportedPropertiesList() {
        return GEOAREA_SUPPORTED_PROPERTIES;
    }
    /**
     * Configures buckets for geo area chart.
     */
    configureBuckets(extendedReferencePoint) {
        const sanitized = sanitizeAreaMeasures(extendedReferencePoint);
        const buckets = limitNumberOfMeasuresInBuckets(sanitized.buckets, NUMBER_MEASURES_IN_BUCKETS_LIMIT);
        const uiConfig = this.getUiConfig();
        const segmentItems = getSegmentItems(buckets);
        const colorMeasures = getColorMeasures(buckets);
        const areaItems = getAreaItems(buckets, uiConfig);
        const configuredBuckets = createAreaConfiguredBuckets(buckets, areaItems, segmentItems, colorMeasures, uiConfig);
        set(sanitized, BUCKETS, configuredBuckets);
        return sanitized;
    }
    /**
     * Validates insight has data defined before rendering.
     */
    checkBeforeRender(insight) {
        if (!insightHasDataDefined(insight)) {
            throw new EmptyAfmSdkError();
        }
        const buckets = insightBuckets(insight);
        if (!hasAreaMinimumData(buckets)) {
            throw new GeoAreaMissingSdkError();
        }
        return true;
    }
    /**
     * Creates execution for geo area chart.
     *
     * @remarks
     * This method is called by the base visualization infrastructure for exports
     * and other external consumers. The internal rendering uses `buildLayerExecution()`
     * which works with the layer-based architecture.
     */
    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(GeoAreaConfigurationPanel, { 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 area-specific visualization configuration.
     */
    buildGeoAreaVisualizationConfig(options, supportedControls) {
        const { colorMapping } = super.buildVisualizationConfig(options, supportedControls);
        return buildAreaVisualizationConfig({
            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 } = ctx;
        const primaryExecution = this.getExecution(options, insight, executionFactory);
        const additionalLayerExecutions = this.getExecutions(options, insight, executionFactory) ?? [];
        const geoChartProps = {
            backend: this.backend,
            workspace: this.workspace,
            type: "area",
            execution: primaryExecution,
            executions: additionalLayerExecutions,
            execConfig: options.executionConfig,
            drillableItems,
            config,
            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.buildGeoAreaVisualizationConfig(options, controlsWithFallback);
        const filters = insightFilters(insight);
        const sortBy = createAreaSortForSegment(insight);
        const title = insightTitle(insight);
        const controlsForPrimaryLayer = {
            ...controlsWithFallback,
            ...(fullConfig.colorPalette ? { colorPalette: fullConfig.colorPalette } : {}),
        };
        const primaryLayer = insightLayerToGeoLayer({
            id: AREA_LAYER_ID,
            type: "area",
            buckets: insightBuckets(insight),
            sorts: sortBy,
            properties: { controls: controlsForPrimaryLayer },
            ...(title ? { name: title } : {}),
        });
        if (primaryLayer?.type !== "area") {
            // 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,
        };
    }
    getInsightControlsWithFallback(insight) {
        return extractControls(insight) || {};
    }
    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;
    }
    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=PluggableGeoAreaChart.js.map