// (C) 2007-2020 GoodData Corporation
import compact from "lodash/compact";
import flatMap from "lodash/flatMap";
import get from "lodash/get";
import { AFM, VisualizationObject } from "@gooddata/typings";
import { convertVisualizationObjectExtendedFilter } from "./FilterConverter";
import MeasureConverter from "./MeasureConverter";
import { LOCATION, TOOLTIP_TEXT } from "../../DataLayer/constants/buckets";
import { IVisualizationProperties } from "./model/VisualizationObject";

function convertAttribute(
    attribute: VisualizationObject.IVisualizationAttribute,
    idx: number,
): AFM.IAttribute {
    const alias = attribute.visualizationAttribute.alias;
    const aliasProp = alias ? { alias } : {};
    return {
        displayForm: attribute.visualizationAttribute.displayForm,
        localIdentifier: attribute.visualizationAttribute.localIdentifier || `a${idx + 1}`,
        ...aliasProp,
    };
}

function convertAFM(visualizationObject: VisualizationObject.IVisualizationObjectContent): AFM.IAfm {
    const textualAttributes: AFM.IAttribute[] = getAttributes(visualizationObject.buckets).map(
        convertAttribute,
    );
    const geoAttribute = getGeoAttributeForTooltip(visualizationObject);
    const attributes = geoAttribute ? [...textualAttributes, geoAttribute] : textualAttributes;

    const attrProp = attributes.length ? { attributes } : {};

    const measures: AFM.IMeasure[] = getMeasures(visualizationObject.buckets).map(
        MeasureConverter.convertMeasure,
    );
    const measuresProp = measures.length ? { measures } : {};

    const filters: AFM.CompatibilityFilter[] = visualizationObject.filters
        ? compact(visualizationObject.filters.map(convertVisualizationObjectExtendedFilter))
        : [];
    const filtersProp = filters.length ? { filters } : {};

    const nativeTotals = convertNativeTotals(visualizationObject);
    const nativeTotalsProp = nativeTotals.length ? { nativeTotals } : {};

    return {
        ...measuresProp,
        ...attrProp,
        ...filtersProp,
        ...nativeTotalsProp,
    };
}

function getMeasures(buckets: VisualizationObject.IBucket[]): VisualizationObject.IMeasure[] {
    return buckets.reduce((result: VisualizationObject.IMeasure[], bucket: VisualizationObject.IBucket) => {
        const measureItems: VisualizationObject.IMeasure[] = bucket.items.filter(
            VisualizationObject.isMeasure,
        );

        return result.concat(measureItems);
    }, []);
}

function getNativeTotalAttributeIdentifiers(
    bucket: VisualizationObject.IBucket,
    total: VisualizationObject.IVisualizationTotal,
): string[] {
    const attributes = bucket.items.filter(VisualizationObject.isAttribute);

    const totalAttributeIndex = attributes.findIndex(
        attribute => attribute.visualizationAttribute.localIdentifier === total.attributeIdentifier,
    );

    return attributes
        .slice(0, totalAttributeIndex)
        .map(attribute => attribute.visualizationAttribute.localIdentifier);
}

function convertNativeTotals(
    visObj: VisualizationObject.IVisualizationObjectContent,
): AFM.INativeTotalItem[] {
    const nativeTotalsPerBucket = visObj.buckets.map(bucket => {
        const totals = bucket.totals || [];
        const nativeTotals = totals.filter(total => total.type === "nat");

        return nativeTotals.map(total => ({
            measureIdentifier: total.measureIdentifier,
            attributeIdentifiers: getNativeTotalAttributeIdentifiers(bucket, total),
        }));
    });

    return flatMap(nativeTotalsPerBucket);
}

function getAttributes(
    buckets: VisualizationObject.IBucket[],
): VisualizationObject.IVisualizationAttribute[] {
    return buckets.reduce(
        (result: VisualizationObject.IVisualizationAttribute[], bucket: VisualizationObject.IBucket) => {
            const items: VisualizationObject.IVisualizationAttribute[] = bucket.items.filter(
                VisualizationObject.isAttribute,
            );

            return result.concat(items);
        },
        [],
    );
}

function parsePropertyItem(item: string): IVisualizationProperties {
    try {
        return JSON.parse(item);
    } catch (e) {
        return {};
    }
}

function buildTooltipBucketItem(tooltipText: string, alias: string): AFM.IAttribute {
    const tooltipAlias = alias ? { alias } : {};
    return {
        localIdentifier: TOOLTIP_TEXT,
        displayForm: {
            uri: tooltipText,
        },
        ...tooltipAlias,
    };
}

function getGeoAttributeForTooltip(
    visualizationObject: VisualizationObject.IVisualizationObjectContent,
): AFM.IAttribute | null {
    const properties: IVisualizationProperties = parsePropertyItem(visualizationObject.properties || "");
    const tooltipText: string = get(properties, "controls.tooltipText", "");
    if (tooltipText) {
        // copy alias of Geo attribute to alias of tooltipText attribute
        const locationAlias: string = visualizationObject.buckets.reduce(
            (locationAlias: string, bucket: VisualizationObject.IBucket): string => {
                if (!locationAlias && bucket.localIdentifier === LOCATION) {
                    return get(bucket, "items[0].visualizationAttribute.alias");
                }
                return locationAlias;
            },
            "",
        );
        return buildTooltipBucketItem(tooltipText, locationAlias);
    }

    return null;
}

function convertSorting(visObj: VisualizationObject.IVisualizationObjectContent): AFM.SortItem[] {
    if (visObj.properties) {
        let properties = {};
        try {
            properties = JSON.parse(visObj.properties);
        } catch {
            // tslint:disable-next-line:no-console
            console.error("Properties contains invalid JSON string.");
        }

        const sorts: AFM.SortItem[] = get(properties, "sortItems", []);
        return sorts ? sorts : [];
    }

    return [];
}

function convertResultSpec(visObj: VisualizationObject.IVisualizationObjectContent): AFM.IResultSpec {
    const sorts = convertSorting(visObj);
    // Workaround because we can handle only 1 sort item for now
    const sortsProp = sorts.length ? { sorts: sorts.slice(0, 1) } : {};

    return {
        ...sortsProp,
    };
}

export interface IConvertedAFM {
    afm: AFM.IAfm;
    resultSpec: AFM.IResultSpec;
}

/**
 * Converts visualizationObject to afm and resultSpec
 *
 * @method toAfmResultSpec
 * @param {VisualizationObject.IVisualizationObjectContent} visObj
 * @returns {IConvertedAFM}
 */
export function toAfmResultSpec(visObj: VisualizationObject.IVisualizationObjectContent): IConvertedAFM {
    const afm = convertAFM(visObj);
    return {
        afm,
        resultSpec: convertResultSpec(visObj),
    };
}
