import { serializeQueryForPortal } from "../serializeQueryForPortal";
import { getUser, searchGroupUsers, } from "@esri/arcgis-rest-portal";
import { expandPredicate } from "./expandPredicate";
import { getNextPortalCallback } from "./commonHelpers/getNextPortalCallback";
import HubError from "../../HubError";
import { getProp } from "../../objects/get-prop";
import { pickProps } from "../../objects/pickProps";
import { setProp } from "../../objects/set-prop";
import { enrichUserSearchResult } from "../../users/HubUsers";
import { failSafe } from "../../utils/fail-safe";
/**
 * Search for members of a group.
 * The groupId is specified via a `group` predicate.
 * Any `term` predicate will be re-mapped to `name`.
 *
 * The backing API is very limited in what
 * it returns so this method executes the search and then tries to fetch
 * the user object directly. This is a bit slower but provides a more
 * information. Even in this case, private users may not be returned, and
 * so we have a default user that is returned in those cases.
 *
 * @param query
 * @param options
 * @returns
 */
export async function portalSearchGroupMembers(query, options) {
    // Requires that the query have a filter with a group predicate
    let groupId;
    query.filters.forEach((filter) => {
        filter.predicates.forEach((predicate) => {
            const prop = getProp(predicate, "group");
            if (Array.isArray(prop)) {
                // get first entry from array
                groupId = prop[0];
            }
            else if (typeof prop === "string") {
                // get the value as a string
                groupId = prop;
            }
            else if (typeof prop === "object") {
                // get the value from the object
                // get first entry from any or all array
                groupId = getProp(prop, "any[0]") || getProp(prop, "all[0]");
            }
        });
    });
    if (!groupId) {
        throw new HubError("portalSearchGroupMembers", "Group Id required. Please pass as a predicate in the query.");
    }
    // Expand the individual predicates in each filter
    query.filters = query.filters.map((filter) => {
        // only `name`, `memberType` and `joined` are supported
        const validPredicateKeys = ["name", "memberType", "joined"];
        filter.predicates = filter.predicates
            .map((p) => {
            // convert `term` to `name`
            if (p.term) {
                p.name = p.term;
                delete p.term;
            }
            return p;
        })
            // remove any keys that aren't supported
            .map((p) => {
            return pickProps(p, validPredicateKeys);
        })
            // remove any empty predicates
            .filter((p) => {
            return Object.entries(p).length > 0;
        })
            // expand the remaining predicates
            .map(expandPredicate);
        return filter;
    });
    // Serialize the all the groups for portal
    const so = serializeQueryForPortal(query);
    // Array of properties we want to copy from IHubSearchOptions to the ISearchOptions
    const props = [
        "num",
        "sortField",
        "sortOrder",
        "include",
        "start",
    ];
    // copy the props over
    props.forEach((prop) => {
        if (Object.prototype.hasOwnProperty.call(options, prop)) {
            so[prop] = options[prop];
        }
    });
    so.groupId = groupId;
    so.requestOptions = options.requestOptions;
    // Execute search
    return searchGroupMembers(so);
}
/**
 * @private
 * Portal Search Implementation for Group Members
 * @param options
 */
async function searchGroupMembers(searchOptions) {
    // searchGroupUsers needs requestOptions spread into it's options
    const opts = Object.assign(Object.assign({}, searchOptions), searchOptions.requestOptions);
    const resp = await searchGroupUsers(searchOptions.groupId, opts);
    // create mappable fn that will close
    // over the includes and requestOptions
    const fn = (member) => {
        return memberToSearchResult(Object.assign(Object.assign({}, member), { isGroupOwner: resp.owner.username === member.username }), searchOptions.include, searchOptions.requestOptions);
    };
    // map over results
    const results = await Promise.all(resp.users.map(fn));
    return {
        total: resp.total,
        results,
        hasNext: resp.nextStart > -1,
        next: getNextPortalCallback(searchOptions, resp.nextStart, resp.total, searchGroupMembers),
    };
}
/**
 * Convert an Group Member to a IHubSearchResult
 * Fetches the backing user and uses that to populate the  user object
 * If no user is found, the object is very sparse
 * @param item
 * @param include
 * @param requestOptions
 * @returns
 */
async function memberToSearchResult(member, include = [], requestOptions) {
    // cross org requests may fail for non-public users
    // so we have a default user that has minimal information
    const user = {
        username: member.username,
        memberType: member.memberType || "member",
        access: "private",
        fullName: null,
        firstName: null,
        lastName: null,
        description: null,
        orgId: null,
        groups: [],
        tags: [],
        thumbnail: null,
        created: null,
        modified: null,
        isGroupOwner: member.isGroupOwner,
    };
    const fsGetUser = failSafe(getUser, user);
    const fetchedUser = await fsGetUser({
        username: member.username,
        authentication: requestOptions.authentication,
        portal: requestOptions.portal,
    });
    // Map props from the fetched user, onto the user
    // this is done because the api returns a sparse IGroupMember under some conditions
    // and we'd rather have the default user with keys present than a structure
    // with missing keys
    Object.keys(user).forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(fetchedUser, key) &&
            fetchedUser[key] !== "") {
            setProp(key, fetchedUser[key], user);
        }
    });
    return enrichUserSearchResult(user, ["org.name as OrgName", ...include], requestOptions);
}
//# sourceMappingURL=portalSearchGroupMembers.js.map