import { jsx as _jsx } from "react/jsx-runtime";
// (C) 2019-2026 GoodData Corporation
import { PureComponent, createRef } from "react";
import { isEmpty, isEqual, omit } from "lodash-es";
import { createRoot } from "react-dom/client";
import { v4 as uuidv4 } from "uuid";
import { insightProperties, isInsight, visClassUrl, } from "@gooddata/sdk-model";
import { FullVisualizationCatalog } from "./VisualizationCatalog.js";
import { ConfigPanelClassName, } from "../interfaces/Visualization.js";
export class BaseVisualization extends PureComponent {
    static defaultProps = {
        visualizationCatalog: FullVisualizationCatalog,
        newDerivedBucketItems: [],
        referencePoint: undefined,
        onExtendedReferencePointChanged: () => { },
        onNewDerivedBucketItemsPlaced: () => { },
        isMdObjectValid: true,
        configPanelClassName: ConfigPanelClassName,
        featureFlags: {},
    };
    visElementId;
    visualization;
    executionFactory;
    containerRef;
    /**
     * The component may render both visualization and config panel. In React18 we therefore need two
     * roots with their respective render methods. This Map holds the roots for both and provides
     * render and unmount methods whenever needed.
     */
    reactRootsMap = new Map();
    constructor(props) {
        super(props);
        const { insight } = props;
        let visualizationId;
        if (isInsight(insight)) {
            visualizationId = insight.insight.identifier;
        }
        else {
            visualizationId = "__new_visualization__";
        }
        this.visElementId = uuidv4();
        this.executionFactory = props.backend
            .withCorrelation({ visualizationId })
            .workspace(props.projectId)
            .execution();
        this.containerRef = createRef();
    }
    componentWillUnmount() {
        if (this.visualization) {
            this.visualization.unmount();
        }
        // In order to avoid race conditions when mounting and unmounting synchronously,
        // we use timeout for React18.
        // https://github.com/facebook/react/issues/25675
        this.reactRootsMap.forEach((root) => setTimeout(() => root.unmount(), 0));
    }
    UNSAFE_componentWillReceiveProps(nextProps) {
        const newDerivedBucketItemsChanged = !isEmpty(nextProps.newDerivedBucketItems) &&
            !isEqual(nextProps.newDerivedBucketItems, this.props.newDerivedBucketItems);
        if (newDerivedBucketItemsChanged) {
            this.triggerPlaceNewDerivedBucketItems(nextProps);
            return;
        }
        // buckets changed from within inner visualization logic
        const bucketsToUpdate = this.visualization?.getBucketsToUpdate(this.props.referencePoint, nextProps.referencePoint);
        if (bucketsToUpdate) {
            this.triggerPlaceNewDerivedBucketItems(nextProps, bucketsToUpdate);
            this.triggerExtendedReferencePointChanged(nextProps, this.props);
            return;
        }
        const visualizationClassChanged = !isEqual(nextProps.visualizationClass, this.props.visualizationClass);
        const referencePointChanged = BaseVisualization.bucketReferencePointHasChanged(this.props.referencePoint, nextProps.referencePoint);
        const relevantPropertiesChanged = this.somePropertiesRelevantForReferencePointChanged(this.props.referencePoint, nextProps.referencePoint);
        const labelsChanged = !isEqual(this.props.insight?.insight.attributeFilterConfigs, nextProps.insight?.insight.attributeFilterConfigs);
        const propertiesControlsChanged = BaseVisualization.propertiesControlsHasChanged(this.props.referencePoint, nextProps.referencePoint);
        if (visualizationClassChanged) {
            this.visElementId = uuidv4();
            this.setupVisualization(nextProps);
        }
        if (referencePointChanged ||
            relevantPropertiesChanged ||
            visualizationClassChanged ||
            labelsChanged) {
            this.triggerExtendedReferencePointChanged(nextProps, 
            // only pass current props if the visualization class is the same (see getExtendedReferencePoint JSDoc)
            visualizationClassChanged ? undefined : this.props);
            // Some of the properties eg. stacking of measures, dual axes influence sorting
        }
        else if (propertiesControlsChanged) {
            this.triggerPropertiesChanged(nextProps, this.props);
        }
    }
    componentDidMount() {
        this.setupVisualization(this.props);
        this.updateVisualization();
        this.triggerExtendedReferencePointChanged(this.props);
    }
    componentDidUpdate() {
        if (this.props.isMdObjectValid) {
            this.updateVisualization();
        }
    }
    render() {
        return (_jsx("div", { "aria-label": "base-visualization", style: { height: "100%" }, className: this.getClassName(), ref: this.containerRef }, this.visElementId));
    }
    getVisElementClassName() {
        return `gd-vis-${this.visElementId}`;
    }
    getClassName() {
        return `gd-base-visualization ${this.getVisElementClassName()}`;
    }
    setupVisualization(props) {
        const { insight, visualizationClass, environment, locale, featureFlags, projectId, configPanelClassName, renderer, unmount, } = props;
        if (this.visualization) {
            this.visualization.unmount();
        }
        const visUri = visClassUrl(visualizationClass);
        let visFactory;
        try {
            visFactory = this.props
                .visualizationCatalog.forUri(visUri, featureFlags?.enableNewPivotTable ?? true, featureFlags?.enableNewGeoPushpin ?? false)
                .getFactory();
        }
        catch {
            console.error(`Error: unsupported visualization type - ${visUri}`);
        }
        let visualizationId;
        if (isInsight(insight)) {
            visualizationId = insight.insight.identifier;
        }
        else {
            visualizationId = "__new_visualization__";
        }
        if (visFactory) {
            const constructorParams = {
                projectId,
                locale,
                messages: this.props.messages,
                environment,
                backend: props.backend.withCorrelation({ visualizationId }),
                element: () => {
                    const rootNode = this.containerRef.current?.getRootNode() ?? document;
                    return rootNode.querySelector(`.${this.getVisElementClassName()}`);
                },
                configPanelElement: () => {
                    const rootNode = this.containerRef.current?.getRootNode() ?? document;
                    return rootNode.querySelector(`.${configPanelClassName}`);
                },
                callbacks: {
                    afterRender: props.afterRender,
                    onLoadingChanged: props.onLoadingChanged,
                    onError: props.onError,
                    onExportReady: props.onExportReady,
                    pushData: props.pushData,
                    onDrill: props.onDrill,
                    onDataView: props.onDataView,
                },
                featureFlags,
                visualizationProperties: insightProperties(props.insight),
                renderFun: (renderer ?? this.getReactRenderFunction()),
                unmountFun: unmount ?? this.getReactUnmountFunction(),
            };
            this.visualization = visFactory(constructorParams);
        }
    }
    getReactRenderFunction = () => {
        return (children, element) => {
            if (!element) {
                return;
            }
            if (!this.reactRootsMap.get(element)) {
                this.reactRootsMap.set(element, createRoot(element));
            }
            this.reactRootsMap.get(element).render(children);
        };
    };
    getReactUnmountFunction = () => {
        return () => this.reactRootsMap.forEach((root) => root.render(null));
    };
    updateVisualization() {
        if (!this.visualization) {
            return;
        }
        this.visualization.update(this.getVisualizationProps(), this.props.insight, this.props.insightPropertiesMeta, this.executionFactory);
    }
    triggerPlaceNewDerivedBucketItems(props, newBucketItemsFromVisualization) {
        const { newDerivedBucketItems, referencePoint, onNewDerivedBucketItemsPlaced } = props;
        const newDerivedBucketItemsToPlace = newBucketItemsFromVisualization ?? newDerivedBucketItems;
        if (this.visualization &&
            referencePoint &&
            newDerivedBucketItemsToPlace &&
            onNewDerivedBucketItemsPlaced) {
            void this.visualization
                .addNewDerivedBucketItems(referencePoint, newDerivedBucketItemsToPlace)
                .then(onNewDerivedBucketItemsPlaced);
        }
    }
    triggerExtendedReferencePointChanged(newProps, currentProps) {
        const { referencePoint: newReferencePoint, onExtendedReferencePointChanged } = newProps;
        if (this.visualization && newReferencePoint && onExtendedReferencePointChanged) {
            void this.visualization
                .getExtendedReferencePoint(newReferencePoint, currentProps?.referencePoint)
                .then(async (extendedReferencePoint) => {
                const sortConfig = await this.visualization.getSortConfig(extendedReferencePoint);
                // new sort config needs to be sent together with new reference point to avoid double executions with old invalid sort until new one arrives by its own handler
                onExtendedReferencePointChanged(extendedReferencePoint, sortConfig);
            });
        }
    }
    triggerPropertiesChanged(newProps, currentProps) {
        const { referencePoint: newReferencePoint, onSortingChanged } = newProps;
        // Some of the properties eg. stacking of measures, dual axes influence sorting
        if (this.visualization && newReferencePoint && onSortingChanged) {
            void this.visualization
                .getExtendedReferencePoint(newReferencePoint, currentProps?.referencePoint)
                .then((extendedRefPoint) => {
                void this.visualization.getSortConfig(extendedRefPoint).then(onSortingChanged);
            });
        }
    }
    static bucketReferencePointHasChanged(currentReferencePoint, nextReferencePoint) {
        return !isEqual(omit(currentReferencePoint, ["properties", "availableSorts"]), omit(nextReferencePoint, ["properties", "availableSorts"]));
    }
    static propertiesControlsHasChanged(currentReferencePoint, nextReferencePoint) {
        return !isEqual(currentReferencePoint?.properties?.controls, nextReferencePoint?.properties?.controls);
    }
    somePropertiesRelevantForReferencePointChanged(currentReferencePoint, nextReferencePoint) {
        if (this.visualization) {
            return this.visualization.haveSomePropertiesRelevantForReferencePointChanged(currentReferencePoint, nextReferencePoint);
        }
        return false;
    }
    getVisualizationProps() {
        return {
            locale: this.props.locale,
            dateFormat: this.props.dateFormat,
            dimensions: {
                width: this.props.width,
                height: this.props.height,
            },
            custom: {
                drillableItems: this.props.drillableItems,
                totalsEditAllowed: this.props.totalsEditAllowed,
                lastSavedVisClassUrl: this.props.lastSavedVisClassUrl,
                sourceInsightId: this.props.sourceInsightId,
                configurationPanelRenderers: this.props.configurationPanelRenderers,
            },
            messages: this.props.messages,
            config: this.props.config,
            theme: this.props.theme,
            executionConfig: this.props.executionConfig,
            supportsChartFill: this.props.supportsChartFill,
        };
    }
    getInsightWithDrillDownApplied(sourceVisualization, drillDownContext) {
        return this.visualization.getInsightWithDrillDownApplied(sourceVisualization, drillDownContext, this.props.backend.capabilities.supportsElementUris ?? true);
    }
    getExecution() {
        if (!this.visualization) {
            this.setupVisualization(this.props);
        }
        return this.visualization.getExecution(this.getVisualizationProps(), this.props.insight, this.executionFactory);
    }
    getExecutions() {
        if (!this.visualization) {
            this.setupVisualization(this.props);
        }
        return this.visualization?.getExecutions?.(this.getVisualizationProps(), this.props.insight, this.executionFactory);
    }
}
//# sourceMappingURL=BaseVisualization.js.map