// (C) 2007-2019 GoodData Corporation
import flatMap = require("lodash/flatMap");
import get = require("lodash/get");
import Highcharts from "./highchartsEntryPoint";

import {
    isStacked,
    IRectBySize,
    isIntersecting,
    pointInRange,
    IAxisRange,
    IAxisRangeForAxes,
} from "./helpers";
import { isAreaChart, isOneOfTypes } from "../../utils/common";
import { IDataLabelsVisible } from "../../../../interfaces/Config";
import { BLACK_LABEL, WHITE_LABEL, whiteDataLabelTypes } from "../../../../constants/label";

export function isLabelOverlappingItsShape(point: any) {
    const { dataLabel, shapeArgs } = point;
    if (dataLabel && shapeArgs) {
        // shapeArgs for point hidden by legend is undefined
        if (shapeArgs.width === undefined) {
            return dataLabel.width > shapeArgs.r * 2 || dataLabel.height > shapeArgs.r * 2;
        }
        return dataLabel.width > shapeArgs.width || dataLabel.height > shapeArgs.height;
    }
    return false;
}

export const getDataLabelsGdcVisible = (chart: any): boolean | string =>
    get(chart, "options.plotOptions.gdcOptions.dataLabels.visible", "auto");

const isLabelsStackedFromYAxis = (chart: any) =>
    get(chart, "userOptions.yAxis.0.stackLabels.enabled", false) ||
    get(chart, "userOptions.yAxis.1.stackLabels.enabled", false);

export const areLabelsStacked = (chart: any) => isLabelsStackedFromYAxis(chart) && isStacked(chart);

export const hasDataLabel = (point: any) => point.dataLabel;
export const hasShape = (point: any) => point.shapeArgs;
export const hasLabelInside = (point: any) => {
    const verticalAlign = get(point, "dataLabel.alignOptions.verticalAlign", "");
    return verticalAlign === "middle";
};

export const minimizeDataLabel = (point: any) => {
    const { dataLabel } = point;
    if (dataLabel) {
        dataLabel.width = 0;
        dataLabel.height = 0;
    }
};

export const hideDataLabel = (point: any) => {
    const { dataLabel } = point;
    if (dataLabel) {
        dataLabel.hide();
    }
};

export const showDataLabel = (point: any) => {
    const { dataLabel } = point;
    if (dataLabel) {
        dataLabel.show();
    }
};

export const hideDataLabels = (points: any) => {
    points.filter(hasDataLabel).forEach(hideDataLabel);
};

export const showDataLabels = (points: any) => {
    points.filter(hasDataLabel).forEach(showDataLabel);
};

export interface IInsideResult {
    vertically: boolean;
    horizontally: boolean;
}

export function showDataLabelInAxisRange(point: any, value: number, axisRangeForAxes: IAxisRangeForAxes) {
    const isSecondAxis = get(point, "series.yAxis.opposite", false);
    const axisRange: IAxisRange = axisRangeForAxes[isSecondAxis ? "second" : "first"];
    const isInsideAxisRange: boolean = pointInRange(value, axisRange);
    if (!isInsideAxisRange) {
        hideDataLabel(point);
    }
}

export function showStackLabelInAxisRange(point: any, axisRangeForAxes: IAxisRangeForAxes) {
    const isSecondAxis = get(point, "series.yAxis.opposite", false);
    const axisRange: IAxisRange = axisRangeForAxes[isSecondAxis ? "second" : "first"];
    const end = point.stackY || point.total;
    const start = end - point.y;
    const isWholeUnderMin: boolean = start <= axisRange.minAxisValue && end <= axisRange.minAxisValue;
    const isWholeAboveMax: boolean = start >= axisRange.maxAxisValue && end >= axisRange.maxAxisValue;
    if (isWholeUnderMin || isWholeAboveMax) {
        hideDataLabel(point);
    }
}

export const hideAllLabels = ({ series }: any) => hideDataLabels(flatMap(series, s => s.points));

export const showAllLabels = ({ series }: any) => showDataLabels(flatMap(series, s => s.points));

export function getDataLabelAttributes(point: any): IRectBySize {
    const dataLabel = get(point, "dataLabel", null);
    const parentGroup = get(point, "dataLabel.parentGroup", null);

    const labelSafeOffset = -100; // labels outside axis range have typically -9999, hide them
    const labelVisible = dataLabel && dataLabel.x > labelSafeOffset && dataLabel.y > labelSafeOffset;

    if (dataLabel && parentGroup && labelVisible) {
        return {
            x: dataLabel.x + parentGroup.translateX,
            y: dataLabel.y + parentGroup.translateY,
            width: dataLabel.width,
            height: dataLabel.height,
        };
    }

    return {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
    };
}

export function intersectsParentLabel(point: any, points: any) {
    const pointParent = parseInt(point.parent, 10);
    // Highchart 7 doesn't render dataLabel at points which have null value
    const pointLabelShape = point.dataLabel;
    if (isNaN(pointParent) || !pointLabelShape) {
        return false;
    }

    const parentPoint = points[pointParent];
    const parentLabelShape = parentPoint.dataLabel;
    return isIntersecting(pointLabelShape, parentLabelShape);
}

function isTruncatedByMin(shape: any, chart: any) {
    return shape.y + shape.height > chart.clipBox.height;
}

function isTruncatedByMax(shape: any) {
    return shape.y < 0;
}

// works for both column/bar chart thanks bar's 90deg rotation
export function getShapeVisiblePart(shape: any, chart: any, wholeSize: number) {
    if (isTruncatedByMax(shape)) {
        return shape.y + shape.height;
    } else if (isTruncatedByMin(shape, chart)) {
        return chart.clipBox.height - shape.y;
    }

    return wholeSize;
}

export function getLabelStyle(type: string, stacking: string) {
    if (isAreaChart(type)) {
        return BLACK_LABEL;
    }
    return stacking || isOneOfTypes(type, whiteDataLabelTypes) ? WHITE_LABEL : BLACK_LABEL;
}

/**
 * A callback function to format data label and `this` is required by Highchart
 * Ref: https://api.highcharts.com/highcharts/yAxis.labels.formatter
 */
export function formatAsPercent(unit: number = 100): string {
    const val = parseFloat((this.value * unit).toPrecision(14));
    return `${val}%`;
}

export function isInPercent(format: string = ""): boolean {
    return format.includes("%");
}

export function getLabelsVisibilityConfig(visible: IDataLabelsVisible): Highcharts.DataLabelsOptionsObject {
    switch (visible) {
        case "auto":
            return {
                enabled: true,
                allowOverlap: false,
            };
        case true:
            return {
                enabled: true,
                allowOverlap: true,
            };
        case false:
            return {
                enabled: false,
            };
        default:
            // keep decision on each chart for `undefined`
            return {};
    }
}
