// (C) 2007-2020 GoodData Corporation
import get = require("lodash/get");
import { AFM, VisualizationObject } from "@gooddata/typings";
import { VisualizationTypes, VisType } from "../constants/visualizationTypes";
import { MEASUREGROUP } from "../constants/dimensions";
import { VIEW, STACK, SEGMENT, TREND, ATTRIBUTE, COLUMNS, MEASURES } from "../constants/bucketNames";
import { convertBucketsToAFM } from "../helpers/conversion";
import { VIEW_BY_ATTRIBUTES_LIMIT } from "../components/visualizations/chart/constants";
import { findBucketByLocalIdentifier } from "./mdObjBucketHelper";
import { getGeoBucketsFromMdObject } from "./geoChart/data";

export function getDimensionTotals(bucket: VisualizationObject.IBucket): AFM.ITotalItem[] {
    const bucketTotals: VisualizationObject.IVisualizationTotal[] = get<
        VisualizationObject.IBucket,
        "totals",
        VisualizationObject.IVisualizationTotal[]
    >(bucket, "totals", []);
    return bucketTotals.map(
        (total: VisualizationObject.IVisualizationTotal): AFM.ITotalItem => {
            return {
                measureIdentifier: total.measureIdentifier,
                type: total.type,
                attributeIdentifier: total.attributeIdentifier,
            };
        },
    );
}

export function getPivotTableDimensions(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const rowAttributes: VisualizationObject.IBucket = buckets.find(
        // ATTRIBUTE for backwards compatibility with Table component. Actually ROWS
        bucket => bucket.localIdentifier === ATTRIBUTE,
    );

    const columnAttributes: VisualizationObject.IBucket = buckets.find(
        bucket => bucket.localIdentifier === COLUMNS,
    );

    const measures: VisualizationObject.IBucket = buckets.find(bucket => bucket.localIdentifier === MEASURES);

    const rowAttributesItemIdentifiers: string[] = get<
        VisualizationObject.IBucket,
        "items",
        VisualizationObject.BucketItem[]
    >(rowAttributes, "items", []).map(
        (a: VisualizationObject.IVisualizationAttribute): string => a.visualizationAttribute.localIdentifier,
    );

    const columnAttributesItemIdentifiers: string[] = get<
        VisualizationObject.IBucket,
        "items",
        VisualizationObject.BucketItem[]
    >(columnAttributes, "items", []).map(
        (a: VisualizationObject.IVisualizationAttribute) => a.visualizationAttribute.localIdentifier,
    );

    const measuresItemIdentifiers = get(measures, "items.length") ? [MEASUREGROUP] : [];

    const totals = getDimensionTotals(rowAttributes);
    const totalsProp = totals.length ? { totals } : {};

    return [
        {
            itemIdentifiers: rowAttributesItemIdentifiers,
            ...totalsProp,
        },
        {
            itemIdentifiers: [...columnAttributesItemIdentifiers, ...measuresItemIdentifiers],
        },
    ];
}

export function getTableDimensions(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const attributes: VisualizationObject.IBucket = buckets.find(
        bucket => bucket.localIdentifier === ATTRIBUTE,
    );

    const measures: VisualizationObject.IBucket = buckets.find(bucket => bucket.localIdentifier === MEASURES);

    const attributesItemIdentifiers: string[] = get<
        VisualizationObject.IBucket,
        "items",
        VisualizationObject.BucketItem[]
    >(attributes, "items", []).map(
        (a: VisualizationObject.IVisualizationAttribute): string => a.visualizationAttribute.localIdentifier,
    );

    const measuresItemIdentifiers = measures && measures.items && measures.items.length ? [MEASUREGROUP] : [];

    const totals = getDimensionTotals(attributes);
    const totalsProp = totals.length ? { totals } : {};

    return [
        {
            itemIdentifiers: attributesItemIdentifiers,
            ...totalsProp,
        },
        {
            itemIdentifiers: measuresItemIdentifiers,
        },
    ];
}

function getLocalIdentifierFromAttribute(attribute: VisualizationObject.IVisualizationAttribute): string {
    return attribute.visualizationAttribute.localIdentifier;
}

function getPieOrDonutDimensions(
    mdObject: VisualizationObject.IVisualizationObjectContent,
): AFM.IDimension[] {
    const view = mdObject.buckets.find(bucket => bucket.localIdentifier === VIEW);

    if (view && view.items.length) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP],
            },
            {
                itemIdentifiers: view.items.map(getLocalIdentifierFromAttribute),
            },
        ];
    }

    return [
        {
            itemIdentifiers: [],
        },
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

function getBarDimensions(mdObject: VisualizationObject.IVisualizationObjectContent): AFM.IDimension[] {
    const view: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === VIEW,
    );

    const stack: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === STACK,
    );

    const hasNoStacks = !stack || !stack.items || stack.items.length === 0;

    if (hasNoStacks) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP],
            },
            {
                itemIdentifiers: ((view && view.items) || []).map(getLocalIdentifierFromAttribute),
            },
        ];
    }

    return [
        {
            itemIdentifiers: ((stack && stack.items) || []).map(getLocalIdentifierFromAttribute),
        },
        {
            itemIdentifiers: ((view && view.items) || [])
                .map(getLocalIdentifierFromAttribute)
                .concat([MEASUREGROUP]),
        },
    ];
}

function getAreaDimensions(mdObject: VisualizationObject.IVisualizationObjectContent): AFM.IDimension[] {
    const view: VisualizationObject.IBucket = findBucketByLocalIdentifier(mdObject.buckets, VIEW);

    const stack: VisualizationObject.IBucket = findBucketByLocalIdentifier(mdObject.buckets, STACK);

    const hasNoStacks = !stack || !stack.items || stack.items.length === 0;
    const haveManyViewItems = view && view.items && view.items.length > 1;

    if (haveManyViewItems) {
        // only take first two view items
        const [viewItemIdentifier, stackItemIdentifier] = view.items
            .slice(0, VIEW_BY_ATTRIBUTES_LIMIT)
            .map(getLocalIdentifierFromAttribute);
        return [
            {
                itemIdentifiers: [stackItemIdentifier],
            },
            {
                itemIdentifiers: [viewItemIdentifier, MEASUREGROUP],
            },
        ];
    }

    if (hasNoStacks) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP],
            },
            {
                itemIdentifiers: ((view && view.items) || []).map(getLocalIdentifierFromAttribute),
            },
        ];
    }

    return [
        {
            itemIdentifiers: ((stack && stack.items) || []).map(getLocalIdentifierFromAttribute),
        },
        {
            itemIdentifiers: ((view && view.items) || [])
                .map(getLocalIdentifierFromAttribute)
                .concat([MEASUREGROUP]),
        },
    ];
}

function getLineDimensions(mdObject: VisualizationObject.IVisualizationObjectContent): AFM.IDimension[] {
    const trend: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === TREND,
    );

    const segment: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === SEGMENT,
    );
    const hasNoSegments = !segment || !segment.items || segment.items.length === 0;

    if (hasNoSegments) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP],
            },
            {
                itemIdentifiers: ((trend && trend.items) || []).map(
                    (t: VisualizationObject.IVisualizationAttribute) =>
                        t.visualizationAttribute.localIdentifier,
                ),
            },
        ];
    }

    return [
        {
            itemIdentifiers: ((segment && segment.items) || []).map(
                (s: VisualizationObject.IVisualizationAttribute) => s.visualizationAttribute.localIdentifier,
            ),
        },
        {
            itemIdentifiers: ((trend && trend.items) || [])
                .map(
                    (t: VisualizationObject.IVisualizationAttribute) =>
                        t.visualizationAttribute.localIdentifier,
                )
                .concat([MEASUREGROUP]),
        },
    ];
}

export function getHeadlinesDimensions(): AFM.IDimension[] {
    return [{ itemIdentifiers: ["measureGroup"] }];
}

export function getXirrDimensions(
    mdObject: VisualizationObject.IVisualizationObjectContent,
): AFM.IDimension[] {
    const attribute: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === ATTRIBUTE,
    );

    if (attribute && attribute.items.length) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP, ...attribute.items.map(getLocalIdentifierFromAttribute)],
            },
        ];
    }

    return [
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

function getScatterDimensions(mdObject: VisualizationObject.IVisualizationObjectContent): AFM.IDimension[] {
    const attribute: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === ATTRIBUTE,
    );

    if (attribute && attribute.items.length) {
        return [
            {
                itemIdentifiers: attribute.items.map(getLocalIdentifierFromAttribute),
            },
            {
                itemIdentifiers: [MEASUREGROUP],
            },
        ];
    }

    return [
        {
            itemIdentifiers: [],
        },
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

export function getGeoChartDimensions(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const { attributes = [], measures = [] }: AFM.IAfm = convertBucketsToAFM(buckets);
    const chartDimensions: AFM.IDimension[] = [];

    if (measures.length > 0) {
        chartDimensions.push({ itemIdentifiers: [MEASUREGROUP] });
    }
    chartDimensions.push({ itemIdentifiers: attributes.map((attr: AFM.IAttribute) => attr.localIdentifier) });
    return chartDimensions;
}

// Heatmap
export function getHeatmapDimensionsFromMdObj(
    mdObject: VisualizationObject.IVisualizationObjectContent,
): AFM.IDimension[] {
    const buckets: VisualizationObject.IBucket[] = mdObject.buckets;
    return getHeatmapDimensionsFromBuckets(buckets);
}

export function getHeatmapDimensionsFromBuckets(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const view: VisualizationObject.IBucket = buckets.find(bucket => bucket.localIdentifier === VIEW);

    const stack: VisualizationObject.IBucket = buckets.find(bucket => bucket.localIdentifier === STACK);

    const hasNoStacks = !stack || !stack.items || stack.items.length === 0;

    if (hasNoStacks) {
        return [
            {
                itemIdentifiers: ((view && view.items) || []).map(getLocalIdentifierFromAttribute),
            },
            {
                itemIdentifiers: [MEASUREGROUP],
            },
        ];
    }

    return [
        {
            itemIdentifiers: ((view && view.items) || []).map(getLocalIdentifierFromAttribute),
        },
        {
            itemIdentifiers: ((stack && stack.items) || [])
                .map(getLocalIdentifierFromAttribute)
                .concat([MEASUREGROUP]),
        },
    ];
}

function getBubbleDimensions(mdObject: VisualizationObject.IVisualizationObjectContent): AFM.IDimension[] {
    const view: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === VIEW,
    );
    const stack: VisualizationObject.IBucket = mdObject.buckets.find(
        bucket => bucket.localIdentifier === STACK,
    );
    const hasNoStacks = !stack || !stack.items || stack.items.length === 0;
    if (hasNoStacks) {
        return [
            {
                itemIdentifiers: ((view && view.items) || []).map(getLocalIdentifierFromAttribute),
            },
            {
                itemIdentifiers: [MEASUREGROUP],
            },
        ];
    }

    return [
        {
            itemIdentifiers: ((view && view.items) || [])
                .map(getLocalIdentifierFromAttribute)
                .concat(((stack && stack.items) || []).map(getLocalIdentifierFromAttribute)),
        },
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

/**
 * generateDimensions
 * is a function that generates dimensions based on buckets and visualization objects.
 * WARNING: It duplicates logic from pluggable visualizations.
 *          Remove once react components support pluggable visualizations.
 * @param mdObject:VisualizationObject.IVisualizationObjectContent - metadata object with buckets
 * @param type:VisType - visualization type string
 * @internal
 */
export function generateDimensions(
    mdObject: VisualizationObject.IVisualizationObjectContent,
    type: VisType,
): AFM.IDimension[] {
    switch (type) {
        case VisualizationTypes.TABLE: {
            return getPivotTableDimensions(mdObject.buckets);
        }
        case VisualizationTypes.PIE:
        case VisualizationTypes.DONUT:
        case VisualizationTypes.FUNNEL: {
            return getPieOrDonutDimensions(mdObject);
        }
        case VisualizationTypes.TREEMAP: {
            return getTreemapDimensionsFromMdObj(mdObject);
        }

        case VisualizationTypes.LINE: {
            return getLineDimensions(mdObject);
        }

        case VisualizationTypes.AREA: {
            return getAreaDimensions(mdObject);
        }

        case VisualizationTypes.BAR:
        case VisualizationTypes.COMBO:
        case VisualizationTypes.COMBO2:
        case VisualizationTypes.COLUMN:
        case VisualizationTypes.BULLET: {
            return getBarDimensions(mdObject);
        }
        case VisualizationTypes.HEADLINE: {
            return getHeadlinesDimensions();
        }
        case VisualizationTypes.SCATTER: {
            return getScatterDimensions(mdObject);
        }
        case VisualizationTypes.HEATMAP: {
            return getHeatmapDimensionsFromMdObj(mdObject);
        }
        case VisualizationTypes.BUBBLE: {
            return getBubbleDimensions(mdObject);
        }
        case VisualizationTypes.XIRR: {
            return getXirrDimensions(mdObject);
        }
        case VisualizationTypes.PUSHPIN: {
            const buckets = getGeoBucketsFromMdObject(mdObject);
            return getGeoChartDimensions(buckets);
        }
    }
    return [];
}

export function generateStackedDimensions(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const viewBucket = buckets.find(bucket => bucket.localIdentifier === ATTRIBUTE);
    const stackBucket = buckets.find(bucket => bucket.localIdentifier === STACK);

    const viewByAttributes =
        viewBucket && (viewBucket.items as VisualizationObject.IVisualizationAttribute[]);
    const stackByAttribute =
        stackBucket && (stackBucket.items[0] as VisualizationObject.IVisualizationAttribute);

    const stackByAttributeLocalIdentifier = stackByAttribute
        ? stackByAttribute.visualizationAttribute.localIdentifier
        : undefined;

    const viewByAttributeLocalIdentifiers =
        viewByAttributes && viewByAttributes.map(getLocalIdentifierFromAttribute);

    return [
        {
            itemIdentifiers: stackByAttributeLocalIdentifier ? [stackByAttributeLocalIdentifier] : [],
        },
        {
            itemIdentifiers: viewByAttributeLocalIdentifiers
                ? [...viewByAttributeLocalIdentifiers, MEASUREGROUP]
                : [MEASUREGROUP],
        },
    ];
}

export function generateDefaultDimensions(afm: AFM.IAfm): AFM.IDimension[] {
    return [
        {
            itemIdentifiers: [MEASUREGROUP],
        },
        {
            itemIdentifiers: (afm.attributes || []).map(a => a.localIdentifier),
        },
    ];
}

export function isStackedChart(buckets: VisualizationObject.IBucket[], stackedBuckedName: string = STACK) {
    return buckets.some(bucket => bucket.localIdentifier === stackedBuckedName && bucket.items.length > 0);
}

// for ScatterPlot and BubbleChart
export function generateDefaultDimensionsForPointsCharts(afm: AFM.IAfm): AFM.IDimension[] {
    return [
        {
            itemIdentifiers: (afm.attributes || []).map(a => a.localIdentifier),
        },
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

// for PieChart, DonutChart
export const generateDefaultDimensionsForRoundChart = (afm: AFM.IAfm): AFM.IDimension[] => {
    if ((afm.attributes || []).length === 0) {
        return [
            {
                itemIdentifiers: [],
            },
            {
                itemIdentifiers: [MEASUREGROUP],
            },
        ];
    }

    return [
        {
            itemIdentifiers: [MEASUREGROUP],
        },
        {
            itemIdentifiers: (afm.attributes || []).map(a => a.localIdentifier),
        },
    ];
};

// Treemap
export function getTreemapDimensionsFromMdObj(
    mdObject: VisualizationObject.IVisualizationObjectContent,
): AFM.IDimension[] {
    const buckets: VisualizationObject.IBucket[] = mdObject.buckets;
    return getTreemapDimensionsFromBuckets(buckets);
}

export function getTreemapDimensionsFromBuckets(buckets: VisualizationObject.IBucket[]): AFM.IDimension[] {
    const afm: AFM.IAfm = convertBucketsToAFM(buckets);
    return getTreemapDimensionsFromAFM(afm);
}

export function getTreemapDimensionsFromAFM(afm: AFM.IAfm): AFM.IDimension[] {
    const attributes = afm.attributes || [];
    if (attributes.length === 1) {
        return [
            {
                itemIdentifiers: [MEASUREGROUP],
            },
            {
                itemIdentifiers: attributes.map(a => a.localIdentifier),
            },
        ];
    }

    return [
        {
            itemIdentifiers: attributes.map(a => a.localIdentifier),
        },
        {
            itemIdentifiers: [MEASUREGROUP],
        },
    ];
}

export function getGeneralDimensionsFromAFM(afm: AFM.IAfm): AFM.IDimension[] {
    const attributes = afm.attributes || [];
    const measures = afm.measures || [];
    const dimensions = [];
    if (attributes.length > 0) {
        dimensions.push({
            itemIdentifiers: attributes.map((attribute: AFM.IAttribute) => attribute.localIdentifier),
        });
    }
    if (measures.length > 0) {
        dimensions.push({
            itemIdentifiers: [MEASUREGROUP],
        });
    }
    return dimensions;
}
