"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveMetric = resolveMetric;
const arcgis_rest_feature_service_1 = require("@esri/arcgis-rest-feature-service");
const get_prop_1 = require("../objects/get-prop");
const combineQueries_1 = require("../search/combineQueries");
const portalSearchItems_1 = require("../search/_internal/portalSearchItems");
/**
 * Resolve a Metric into an array of `IMetricFeature` objects.
 * This implementation is similar to DynamicValues, but is just different
 * enough to warrant a separate implementation.
 * @param metric
 * @param context
 * @returns
 */
async function resolveMetric(metric, context) {
    // At this point the source references should have been resolved
    // so we can force case to a MetricSource and switch on the type
    const source = metric.source;
    // call the appropriate resolver
    switch (source.type) {
        case "static-value":
            return resolveStaticValueMetric(metric, context);
        case "service-query":
            return resolveServiceQueryMetric(metric, context);
        case "item-query":
            return resolveItemQueryMetric(metric, context);
        default:
            throw new Error(`Unknown metric type passed in.`);
    }
}
/**
 * Resolve a static value metric
 * @param metric
 * @param context
 * @returns
 */
function resolveStaticValueMetric(metric, _context) {
    const source = metric.source;
    // cut off the parent identifier from the metric id and use that
    // as the output field name
    const fieldName = metric.id.split("_")[0];
    const result = {
        attributes: {
            id: metric.entityInfo.id,
            name: metric.entityInfo.name,
            type: metric.entityInfo.type,
            [fieldName]: source.value,
            valueType: source.valueType,
        },
    };
    return Promise.resolve({
        features: [result],
        generatedAt: new Date().getTime(),
    });
}
/**
 * Exequte a query against a service and return the aggregate value
 * in an `IMetricFeature`
 * @param metric
 * @param context
 * @returns
 */
async function resolveServiceQueryMetric(metric, context) {
    const source = metric.source;
    // cut off the parent identifier from the metric id and use that
    // as the output field name
    const fieldName = metric.id.split("_")[0];
    // If no where is provided, default to "1=1"
    source.where = source.where || "1=1";
    // Construct the stats definition
    const statsDef = {
        onStatisticField: source.field,
        statisticType: source.statistic,
        outStatisticFieldName: source.field,
    };
    // construct the url by appending the layer id if provided
    const serviceUrl = source.serviceUrl + `/${source.layerId}`;
    // Execute the query
    const response = await (0, arcgis_rest_feature_service_1.queryFeatures)({
        url: serviceUrl,
        where: source.where,
        f: "json",
        outStatistics: [statsDef],
        authentication: context.requestOptions.authentication,
    });
    // TODO: If we enable more stats, we will need to update
    // how we fetch the values out of the response
    const aggregate = (0, get_prop_1.getProp)(response, `features[0].attributes.${source.field}`);
    const result = {
        attributes: {
            id: metric.entityInfo.id,
            name: metric.entityInfo.name,
            type: metric.entityInfo.type,
            [fieldName]: aggregate,
        },
    };
    return {
        features: [result],
        generatedAt: new Date().getTime(),
    };
}
async function resolveItemQueryMetric(metric, context) {
    const source = metric.source;
    // cut off the parent identifier from the metric id and use that
    // as the output field name
    const fieldName = metric.id.split("_")[0];
    // create the query from the source properties
    const predicate = {
        typekeywords: source.keywords,
    };
    if (source.itemTypes) {
        predicate.type = source.itemTypes;
    }
    const baseQuery = {
        targetEntity: "item",
        filters: [
            {
                operation: "AND",
                predicates: [predicate],
            },
        ],
    };
    const scope = source.scope || {
        targetEntity: "item",
        filters: [],
    };
    const combined = (0, combineQueries_1.combineQueries)([baseQuery, scope]);
    const opts = {
        requestOptions: context.hubRequestOptions,
        num: 100,
    };
    // Memoization is great but we have UI's where we want immediate
    // updates as we share more items into the groups, which means
    // we can't memoize the search response
    // create/get memoized version of portalSearchItemsAsItems
    // const memoizedPortalSearch = memoize(portalSearchItemsAsItems);
    // Execute the query
    // const response = await memoizedPortalSearch(combined, opts);
    const response = await (0, portalSearchItems_1.portalSearchItemsAsItems)(combined, opts);
    // This next section is all promise based so that a dynamic value
    // can itself be a dynamic value definition, which then needs to be
    // resolved.
    const promises = await response.results.reduce(async (valsPromise, item) => {
        const vals = await valsPromise;
        const valueFromItem = (0, get_prop_1.getProp)(item, source.propertyPath);
        const result = {
            attributes: {
                id: item.id,
                name: item.title,
                type: item.type,
                [fieldName]: null,
            },
        };
        // Handle case where value is not found...
        // e.g. the project exists but the metric is not defined yet
        if (valueFromItem) {
            // This block is needed if we are just plucking a value from the item
            // e.g. item.views or item.access
            if (typeof valueFromItem === "string" ||
                typeof valueFromItem === "number") {
                result.attributes[fieldName] = valueFromItem;
                vals.push(result);
            }
            else {
                const vfm = valueFromItem;
                // valueFromItem is itself a metric so we call resolveDynamicValues again
                // attach in the entity info, so it's present for the next level of recursion
                vfm.entityInfo = result.attributes;
                const vResult = await resolveMetric(valueFromItem, context);
                vals.push(...vResult.features);
            }
        }
        return vals;
    }, Promise.resolve([]));
    // let everything resolve
    const outputs = await Promise.all(promises);
    // return the results
    return {
        features: outputs,
        generatedAt: new Date().getTime(),
    };
}
//# sourceMappingURL=resolveMetric.js.map