import { autoFormatter, createFormatter, findAllMeasures } from "../../../../utils/index.js";
import { isDimensionSelector, isFieldSelector, isMeasureSelector, isPartialDatumSelector } from "../../../../../dataSelector/index.js";
import { isEmpty } from "remeda";
import { ANNOTATION_Z_INDEX } from "../../../../utils/constant.js";
import { buildDifferenceCoordinateDatum, buildDifferenceText, getDifferenceLineStackResolveMode, getRuntimeDifferenceValue, inferDifferenceBracketDirection, inferDifferenceConnectDirection, resolveDifferenceAnchor, usesDifferenceLineElementStackEnd } from "./annotationDifferenceLineCommon.js";
const DEFAULT_LINE_COLOR = '#BCC1CB';
const DEFAULT_TEXT_COLOR = '#ffffff';
const DEFAULT_TEXT_BACKGROUND_COLOR = '#BCC1CB';
const DEFAULT_TEXT_FONT_SIZE = 12;
const DEFAULT_EXPAND_DISTANCE = 24;
const DEFAULT_LINE_WIDTH = 2;
const DEFAULT_CORNER_RADIUS = 4;
const DEFAULT_LABEL_PADDING = 4;
const DEFAULT_END_SYMBOL_SIZE = 12;
const DEFAULT_END_SYMBOL_REF_X = -4;
const DEFAULT_BRACKET_EXPAND_DISTANCE = 80;
const DEFAULT_BRACKET_LINE_DASH = [
    2,
    2
];
const DEFAULT_PERCENT_DIFFERENCE_FORMAT = {
    type: 'percent',
    fractionDigits: 2
};
const getDifferenceLinePath = (index, total)=>1 === total ? 'annotationDifferenceLine' : `annotationDifferenceLine[${index}]`;
const toArray = (value)=>{
    if (Array.isArray(value)) return value;
    return null == value ? [] : [
        value
    ];
};
const getAxisFormatter = (spec)=>{
    const valueAxisOrient = 'horizontal' === spec.direction ? 'bottom' : 'left';
    const formatMethod = spec.axes?.find((axis)=>axis.orient === valueAxisOrient)?.label?.formatMethod;
    return 'function' == typeof formatMethod ? (value)=>String(formatMethod(value) ?? value) : void 0;
};
const getExplicitMeasureFormat = (measure)=>{
    if (measure?.numFormat && !isEmpty(measure.numFormat)) return measure.numFormat;
    if (measure?.format && !isEmpty(measure.format)) return measure.format;
};
const inferMeasureIdFromDatum = (anchor, measureIds)=>{
    const candidateValues = [
        anchor.matchedDatum,
        anchor.coordinateDatum
    ];
    for (const datum of candidateValues){
        if (!datum) continue;
        const candidates = measureIds.filter((measureId)=>Number(datum[measureId]) === anchor.value);
        if (1 === candidates.length) return candidates[0];
    }
};
const inferMeasureIdFromSelector = (selectorValue, measureIdSet)=>{
    const matchedMeasureIds = new Set();
    for (const currentSelector of toArray(selectorValue)){
        if (isMeasureSelector(currentSelector)) {
            if (measureIdSet.has(currentSelector.field)) matchedMeasureIds.add(currentSelector.field);
            continue;
        }
        if (!(isFieldSelector(currentSelector) || isDimensionSelector(currentSelector))) {
            if (isPartialDatumSelector(currentSelector)) Object.keys(currentSelector).forEach((field)=>{
                if (measureIdSet.has(field)) matchedMeasureIds.add(field);
            });
        }
    }
    return 1 === matchedMeasureIds.size ? Array.from(matchedMeasureIds)[0] : void 0;
};
const resolveDifferenceMeasureId = (anchor, selectorValue, measureIds)=>{
    if ('element' !== anchor.mode) return;
    if (1 === measureIds.length) return measureIds[0];
    const measureIdSet = new Set(measureIds);
    return inferMeasureIdFromSelector(selectorValue, measureIdSet) ?? inferMeasureIdFromDatum(anchor, measureIds);
};
const assertDifferenceLineConfig = (value, path)=>{
    if ('object' != typeof value || null === value || Array.isArray(value)) throw new Error(`${path} must be an object`);
    const start = value.start;
    if ('object' != typeof start || null === start || Array.isArray(start)) throw new Error(`${path}.start is required`);
    if (null == start.selector) throw new Error(`${path}.start.selector is required`);
    const end = value.end;
    if ('object' != typeof end || null === end || Array.isArray(end)) throw new Error(`${path}.end is required`);
    if (null == end.selector) throw new Error(`${path}.end.selector is required`);
};
const annotationDifferenceLine_annotationDifferenceLine = (spec, context)=>{
    const { advancedVSeed, vseed } = context;
    const annotationDifferenceLine = advancedVSeed.annotation?.annotationDifferenceLine;
    if (!annotationDifferenceLine) return spec;
    const theme = advancedVSeed.config?.[vseed.chartType]?.annotation?.annotationDifferenceLine;
    const annotationDifferenceLineList = Array.isArray(annotationDifferenceLine) ? annotationDifferenceLine : [
        annotationDifferenceLine
    ];
    const dataset = advancedVSeed.dataset.flat();
    const chartSpec = spec;
    const stackResolveMode = getDifferenceLineStackResolveMode(vseed, advancedVSeed);
    const useElementStackEnd = usesDifferenceLineElementStackEnd(vseed, advancedVSeed);
    const isBracketChart = 'line' === vseed.chartType || 'area' === vseed.chartType;
    const measures = findAllMeasures(advancedVSeed.measures);
    const measureIds = measures.map((measure)=>measure.id);
    const axisFormatter = getAxisFormatter(chartSpec);
    const percentFormatter = createFormatter(DEFAULT_PERCENT_DIFFERENCE_FORMAT);
    const markLine = annotationDifferenceLineList.flatMap((annotationDifferenceLine, index)=>{
        try {
            assertDifferenceLineConfig(annotationDifferenceLine, getDifferenceLinePath(index, annotationDifferenceLineList.length));
            const start = resolveDifferenceAnchor({
                dataset,
                selectorLabel: 'start',
                selectorValue: annotationDifferenceLine.start.selector,
                spec: chartSpec,
                stackResolveMode,
                allowSelectorFallback: !useElementStackEnd
            });
            const end = resolveDifferenceAnchor({
                dataset,
                selectorLabel: 'end',
                selectorValue: annotationDifferenceLine.end.selector,
                spec: chartSpec,
                stackResolveMode,
                allowSelectorFallback: !useElementStackEnd
            });
            if (!start || !end) return [];
            if (start.mode !== end.mode) return [];
            const usesRuntimeStackEnd = useElementStackEnd || ('column' === vseed.chartType || 'bar' === vseed.chartType) && 'element' === start.mode && 'auto' === stackResolveMode;
            const useBracketStyle = isBracketChart || ('column' === vseed.chartType || 'bar' === vseed.chartType) && 'element' === start.mode && 'auto' === stackResolveMode;
            const isStackedBarElementBracket = 'bar' === vseed.chartType && 'element' === start.mode && 'auto' === stackResolveMode;
            const connectDirection = useBracketStyle ? isStackedBarElementBracket ? 'top' : inferDifferenceBracketDirection(start, end) : inferDifferenceConnectDirection(vseed, [
                start.value,
                end.value
            ]);
            const lineColor = annotationDifferenceLine.lineColor ?? theme?.lineColor ?? DEFAULT_LINE_COLOR;
            const textColor = annotationDifferenceLine.textColor ?? theme?.textColor ?? DEFAULT_TEXT_COLOR;
            const textBackgroundColor = annotationDifferenceLine.textBackgroundColor ?? theme?.textBackgroundColor ?? DEFAULT_TEXT_BACKGROUND_COLOR;
            const textFontSize = annotationDifferenceLine.textFontSize ?? theme?.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
            const differenceType = annotationDifferenceLine.differenceType ?? 'absolute';
            const startMeasureId = resolveDifferenceMeasureId(start, annotationDifferenceLine.start.selector, measureIds);
            const endMeasureId = resolveDifferenceMeasureId(end, annotationDifferenceLine.end.selector, measureIds);
            const sameMeasure = void 0 !== startMeasureId && startMeasureId === endMeasureId;
            const explicitMeasureFormat = sameMeasure ? getExplicitMeasureFormat(measures.find((measure)=>measure.id === startMeasureId)) : void 0;
            const differenceFormatter = 'percent' === differenceType ? percentFormatter : explicitMeasureFormat ? createFormatter(explicitMeasureFormat) : axisFormatter ?? autoFormatter;
            const label = usesRuntimeStackEnd ? {
                visible: true,
                position: 'middle',
                refY: 0,
                formatMethod: (_markData, seriesData)=>{
                    try {
                        return buildDifferenceText(getRuntimeDifferenceValue({
                            anchor: start,
                            seriesData,
                            useElementStackEnd: usesRuntimeStackEnd
                        }), getRuntimeDifferenceValue({
                            anchor: end,
                            seriesData,
                            useElementStackEnd: usesRuntimeStackEnd
                        }), differenceType, differenceFormatter);
                    } catch  {
                        return '';
                    }
                },
                style: {
                    fill: textColor,
                    fontSize: textFontSize
                },
                labelBackground: {
                    visible: true,
                    padding: DEFAULT_LABEL_PADDING,
                    style: {
                        fill: textBackgroundColor,
                        fillOpacity: 1,
                        stroke: lineColor,
                        lineWidth: 1,
                        cornerRadius: DEFAULT_CORNER_RADIUS
                    }
                }
            } : {
                visible: true,
                position: 'middle',
                refY: 0,
                text: buildDifferenceText(start.value, end.value, differenceType, differenceFormatter),
                style: {
                    fill: textColor,
                    fontSize: textFontSize
                },
                labelBackground: {
                    visible: true,
                    padding: DEFAULT_LABEL_PADDING,
                    style: {
                        fill: textBackgroundColor,
                        fillOpacity: 1,
                        stroke: lineColor,
                        lineWidth: 1,
                        cornerRadius: DEFAULT_CORNER_RADIUS
                    }
                }
            };
            return [
                {
                    type: 'type-step',
                    autoRange: true,
                    zIndex: ANNOTATION_Z_INDEX,
                    connectDirection,
                    expandDistance: useBracketStyle ? DEFAULT_BRACKET_EXPAND_DISTANCE : DEFAULT_EXPAND_DISTANCE,
                    coordinates: (seriesData, relativeSeries)=>{
                        try {
                            return [
                                buildDifferenceCoordinateDatum({
                                    anchor: start,
                                    seriesData,
                                    relativeSeries,
                                    useElementStackEnd: usesRuntimeStackEnd
                                }),
                                buildDifferenceCoordinateDatum({
                                    anchor: end,
                                    seriesData,
                                    relativeSeries,
                                    useElementStackEnd: usesRuntimeStackEnd
                                })
                            ];
                        } catch  {
                            return [];
                        }
                    },
                    line: useBracketStyle ? {
                        multiSegment: true,
                        mainSegmentIndex: 1,
                        style: [
                            {
                                visible: true,
                                stroke: lineColor,
                                lineWidth: DEFAULT_LINE_WIDTH,
                                lineDash: DEFAULT_BRACKET_LINE_DASH
                            },
                            {
                                visible: true,
                                stroke: lineColor,
                                lineWidth: DEFAULT_LINE_WIDTH
                            },
                            {
                                visible: true,
                                stroke: lineColor,
                                lineWidth: DEFAULT_LINE_WIDTH,
                                lineDash: DEFAULT_BRACKET_LINE_DASH
                            }
                        ]
                    } : {
                        style: {
                            visible: true,
                            stroke: lineColor,
                            lineWidth: DEFAULT_LINE_WIDTH,
                            lineDash: [
                                0
                            ],
                            cornerRadius: DEFAULT_CORNER_RADIUS
                        }
                    },
                    label,
                    startSymbol: {
                        visible: false
                    },
                    endSymbol: {
                        visible: true,
                        size: DEFAULT_END_SYMBOL_SIZE,
                        refX: DEFAULT_END_SYMBOL_REF_X,
                        style: {
                            fill: lineColor
                        }
                    }
                }
            ];
        } catch  {
            return [];
        }
    });
    const specMarkLine = chartSpec.markLine || [];
    return {
        ...spec,
        markLine: [
            ...specMarkLine,
            ...markLine
        ]
    };
};
export { annotationDifferenceLine_annotationDifferenceLine as annotationDifferenceLine };

//# sourceMappingURL=annotationDifferenceLine.js.map