import type { ChartOptions } from "../../../chart-rendering/common/interfaces.js";
import type { DataUpdateDetails } from "../../data/interfaces.js";
import type { ChartSubType } from "../../misc/interfaces.js";
import type { WebChartSeriesSupportingTemporalBinning } from "./interfaces.js";
import type { WebChartTypes, WebChartVersionStatus } from "../../../spec/chart-object-literals.js";
import type { WebBoxPlot, WebChart, WebChartLegend, WebChartSeriesType, WebHeatChart } from "../../../spec/web-chart.js";
import type { RESTSymbolType } from "../../../spec/rest-js-object-literals.js";
import type { TimeIntervalInfo } from "../../../spec/data-source.js";
import type { IField } from "../../../spec/rest-js-types.js";

/**
 * Retrieve and construct chart version information based on the input version and the currently supported ArcGIS Chart versions
 * defined in `chartConfigVersions`.  Returned information includes:
 *
 * - versionStatus: whether the input chart version is the same | older | newer | unrecognized, comparing with the current chart version,
 * which is defined in `WebChartCurrentVersion`.  An unrecognized version is one that is neither undefined nor conformed to the
 * sematic versioning spec, that is `Major.Minor.Patch`
 * - versions: an array of versions that will be used in the `appConversion()`.
 *  + For any version that is not recognized, versions will be set to `unrecognizedVersion`;
 *  + If the input version is the same or newer, versions will be the input version;
 *  + Otherwise, versions will be a list of versions that is older and closer to the input version, plus all the supported versions
 * that are newer than the input version.
 *
 * Note: The input chart version needs to conform to the format defined by sematic versioning spec; otherwise it will be treated as unrecognized.
 *
 * @param configVersion - The input config version.
 * @internal
 */
export function getChartVersionInfo(configVersion?: string): {
    versionStatus: WebChartVersionStatus;
    versions: string[];
};

/**
 * Get the series to use as a reference for the chart. That's the one containing important common information like the query,
 * the split-by field, etc.
 * For box plot we need to handle the specific case of the mean line series.
 *
 * @param chartConfig - The chart config
 * @internal
 */
export function getSeriesReference(chartConfig: WebChart): WebChartSeriesType;

/**
 * Find the expected symbol type for a guide given the start and end values.
 *
 * @param start - The guide to analyze
 * @param end
 * @internal
 */
export function findExpectedGuideSymbol(start: Date | number | string | null, end: Date | number | string | null | undefined): RESTSymbolType;

/**
 * Extract the field name from equality-where clauses like `field = 'some_value'`.
 * This field is actually stored in each series where clause.
 * TODO: do we want to enforce the test by seeing if the extracted field is part of the feature layer list of fields?
 * E.g.:
 * "field = 'some_value'" => "field"
 * "field > 'some_value'" => ""
 *
 * @param where - the where clause to extract the field from
 * @param normalize - whether to normalize the extracted field; by default it's true
 * @internal
 */
export function getSplitByField(where: string | undefined, normalize?: boolean): string;

/**
 * Extract the value from equality-where clauses like `field = 'some_value'`.
 * Optionally normalize or escape single quotes in the returned value according to the `normalize` param value.
 * If normalize is undefined then normalize the returned value.
 * For string value, if normalize is set to false and escape is set to false or undefined, the value is returned as is.
 *
 * @param props - the where clause to extract the value from
 * @internal
 */
export function getSplitByValue(props: {
    where: string | undefined;
    normalize?: boolean;
    keepSurroundingQuotes?: boolean;
    escape?: boolean;
}): number | string | null;

/**
 * Detects a where clause being always true, as in 1=1, 2 = 2, 'some_value'='some_value'
 *
 * @param where
 * @internal
 */
export function isAbsoluteTruthWhereClause(where?: string): boolean;

/**
 * Normalizes a where clause, by escaping the "'" character and other special characters if needed.
 * Ex:
 *   - where = "field = 5" => returns "field = 5"
 *   - where = "field = 'value'" => returns "field = 'value'"
 *   - where = "field = 'va'lue'" => returns "field = 'va''lue'"
 *
 * @param props - the where clause to normalize
 * @internal
 */
export function normalizeWhereClause(props: {
    where: string;
    normalize?: boolean;
    escape?: boolean;
}): string;

/**
 * Gets the query type for box plot chart.
 *
 * @param seriesConfig - the box plot series config
 * @internal
 */
export function getBoxPlotChartSubType(seriesConfig: WebChartSeriesType[]): ChartSubType;

/**
 * Returns the type of query we're dealing with. The actual values are:
 * a. Simple (0): single query using an aggregation
 * b. NoAggregation (1): single or multiple queries not using any aggregation
 * c. SplitByFieldValues (2): multiple queries using a specific field as a split-by
 * d. SplitByNumericalFields (3): multiple queries using multiple numerical fields as a split-by
 * e. PieFromFields (4): single query using more than one statistics fields
 * f. For Box-plot: one of the 7 combinations.
 *
 * @param series - The config series. Refrain from using `[firstSeries]` in place of `config.series` to avoid incorrect result,
 * especially for box-plot, bar and line charts.
 * @internal
 */
export function getChartSubType(series?: WebChartSeriesType[]): ChartSubType;

/**
 * Indicates whether the bar or line series is configured to use time binning.
 *
 * @param props - The series configuration
 * @internal
 */
export function isSeriesUsingTimeBinning(props: {
    seriesConfig: WebChartSeriesType;
    xAxisValueFormatType?: WebChartTypes;
    setTimeBinningInfoWhenNotProvided?: boolean;
}): boolean;

/**
 * Indicates whether a series supports temporal binning.
 *
 * @param series - The series to test
 * @internal
 */
export function doesSeriesSupportTemporalBinning(series: WebChartSeriesType): series is WebChartSeriesSupportingTemporalBinning;

/**
 * Compares two config data filters and returns true if they are identical, false otherwise
 *
 * @param previousConfig
 * @param newConfig
 * @internal
 */
export function hasConfigFilterChanged(previousConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Compares two arrays of series and indicates whether the number of series is different.
 *
 * @param previousSeries
 * @param newSeries
 */
export function hasNumberOfSeriesChanged(previousSeries: WebChart["series"], newSeries: WebChart["series"]): boolean;

/**
 * Compares two heat chart configs and indicates whether the hide empty rows and columns option changed
 *
 * @param previousConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasHeatChartHideEmptyRowsAndColumnsChanged(previousConfig: WebHeatChart, newConfig: WebHeatChart): boolean;

/**
 * Compares two heat chart configs and indicates whether to include leap day
 *
 * @param previousConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasHeatChartIncludeLeapDayChanged(previousConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Compares two configs and indicates whether additional tooltip fields (only non-numeric) changed
 *
 * **NOTE**: Only applicable to scatterplot
 *
 * @param props - The previous config
 * @internal
 */
export function hasAdditionalTooltipFieldsChanged(props: {
    previousConfig: WebChart;
    newConfig: WebChart;
    fieldList?: IField[];
}): boolean;

/**
 * Compares two configs and indicates whether the color type change
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasColorMatchChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Compares two configs and indicates whether the stacked type changed
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasStackedTypeChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Normalizes the legend position
 *
 * @param legend - The legend
 * @returns The normalized legend
 * @internal
 */
export function normalizeLegendPosition(legend: WebChartLegend): WebChartLegend;

/**
 * Compares two configs and indicates whether the legend visibility changed
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasHeatLegendConfigChanged(oldConfig: WebHeatChart, newConfig: WebHeatChart): boolean;

/**
 * Compates two configs and indicates whether the heat legend position changed
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasHeatLegendPositionChanged(oldConfig: WebHeatChart, newConfig: WebHeatChart): boolean;

/**
 * Compares two configs and indicates whether the gradient rules changed
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasGradientRulesChanged(oldConfig: WebHeatChart, newConfig: WebHeatChart): boolean;

/**
 * Compares two configs and indicates whether the heat rules type changed
 *
 * @param oldConfig - The previous config
 * @param newConfig - The updated config
 * @internal
 */
export function hasHeatRulesTypeChanged(oldConfig: WebHeatChart, newConfig: WebHeatChart): boolean;

/**
 * Compares the 'slices' array from the pie series, and indicates whether they are different.
 * Compare only the slices id and label to avoid false positives.
 *
 * @param props - The previous chart config
 * @internal
 */
export function hasSlicesConfigChanged(props: {
    oldConfig: WebChart;
    newConfig: WebChart;
}): boolean;

/**
 * @param props
 * @internal
 */
export function hasDataForFilterBySelectionChanged(props: {
    previousSelectionOIDs?: (number | string)[];
    options?: ChartOptions;
}): boolean;

/**
 * Compares the split by value for each series element in the box plot config and returns true if the series has different split by value between new and old box plot config, false otherwise.
 *
 * @param oldSeries - The former series config
 * @param newSeries - The new series config
 * @internal
 */
export function hasSplitByValueChanged(oldSeries: WebChart["series"], newSeries: WebChart["series"]): boolean;

/**
 * Verify if the order of the numeric fields has changed
 *
 * @param oldSeries - The previous series config
 * @param newSeries - The new series config
 * @internal
 */
export function hasNumericFieldsChanged(oldSeries: WebChart["series"], newSeries: WebChart["series"]): boolean;

/**
 * Returns true when the time interval info was set previously and are not in the new config
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasTimeIntervalInfoBeenReset(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true when the dual axis feature was enabled or disabled (number of axes changed)
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasDualAxisChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true when the series are not associated to the same axis anymore
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasSeriesAxisChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true when at least one guide's 'above' property changed from an update to another.
 *
 * @param oldConfig - The old chart config
 * @param newConfig - The new chart config
 * @internal
 */
export function hasAnyGuideAboveChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true if the rotated property changed since the previous update.
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasRotationChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true if the rotated property changed since the previous update.
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasOutliersVisibilityChanged(oldConfig: WebBoxPlot, newConfig: WebBoxPlot): boolean;

/**
 * Returns true if the 'standardizeValues' property changed in the chart config since the previous update.
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasStandardizeValuesVisibilityChanged(oldConfig: WebBoxPlot, newConfig: WebBoxPlot): boolean;

/**
 * Returns true if the 'chartRenderer' property changed in the chart config since the previous update.
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasChartRendererChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Returns true if the 'timeZone' property from the axis value format changed in the chart config since the previous update.
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasIntlTimeZoneChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Indicates whether the size policy changed from the previous update
 *
 * @param oldConfig - the old chart config
 * @param newConfig - the new chart config
 * @internal
 */
export function hasSizePolicyChanged(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Indicates whether a series reinitialization is necessary due to size policy changes
 *
 * @param oldConfig - The old chart config
 * @param newConfig - The new chart config
 * @internal
 */
export function doSizePolicyChangesRequireSeriesReinit(oldConfig: WebChart, newConfig: WebChart): boolean;

/**
 * Function saying if a data update is needed. We compare different properties from the former config and the new one
 * in order to decide if the update is needed.
 *
 * @param props
 * @internal
 */
export function doesConfigChangeRequireNewData(props: {
    previousConfig: WebBoxPlot | WebChart;
    newConfig: WebBoxPlot | WebChart;
    options?: ChartOptions;
    previousFilterBySelection?: boolean;
    previousTimeIntervalInfo?: TimeIntervalInfo;
    previousSelectionOIDs?: (number | string)[];
    fieldList?: IField[];
}): {
    dataUpdateNeeded: boolean;
    dataUpdateDetails: DataUpdateDetails;
};

/**
 * Sorts the series in the config based on the series sort options.
 * When no sort option is provide, no sorting is performed.
 *
 * @param chartConfig - The optional chart config to sort the series
 * @param chartContainer - The HTML chart container
 * @returns The updated chart config with the series sorted, or undefined if the input config is undefined
 * @internal
 */
export function sortConfigSeries<T extends WebChart | undefined>(chartConfig: T, chartContainer?: HTMLElement): T;