/*
 * Copyright 2023 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as OntologiesV2 from "@osdk/foundry.ontologies";
import invariant from "tiny-invariant";
import { extractNamespace } from "../internal/conversions/extractNamespace.js";
import { addUserAgentAndRequestContextHeaders } from "../util/addUserAgentAndRequestContextHeaders.js";
import { extractObjectOrInterfaceType } from "../util/extractObjectOrInterfaceType.js";
import { extractRdpDefinition } from "../util/extractRdpDefinition.js";
import { resolveBaseObjectSetType } from "../util/objectSetUtils.js";
export function augment(type, ...properties) {
  return {
    [type.apiName]: properties
  };
}

/** @internal */
export function objectSetToSearchJsonV2(objectSet, expectedApiName, existingWhere = undefined) {
  if (objectSet.type === "base" || objectSet.type === "interfaceBase") {
    if (objectSet.type === "base" && objectSet.objectType !== expectedApiName) {
      throw new Error(`Expected objectSet.objectType to be ${expectedApiName}, but got ${objectSet.objectType}`);
    }
    if (objectSet.type === "interfaceBase" && objectSet.interfaceType !== expectedApiName) {
      throw new Error(`Expected objectSet.objectType to be ${expectedApiName}, but got ${objectSet.interfaceType}`);
    }
    return existingWhere;
  }
  if (objectSet.type === "filter") {
    return objectSetToSearchJsonV2(objectSet.objectSet, expectedApiName, existingWhere == null ? objectSet.where : {
      type: "and",
      value: [existingWhere, objectSet.where]
    });
  }
  throw new Error(`Unsupported objectSet type: ${objectSet.type}`);
}

/** @internal */
export function resolveInterfaceObjectSet(objectSet, interfaceTypeApiName, args) {
  return args?.$includeAllBaseObjectProperties ? {
    type: "intersect",
    objectSets: [objectSet, {
      type: "interfaceBase",
      interfaceType: interfaceTypeApiName,
      includeAllBaseObjectProperties: true
    }]
  } : objectSet;
}

/** @internal */
export async function fetchStaticRidPage(client, rids, args, useSnapshot = false) {
  const shouldLoadPropertySecurities = args.$loadPropertySecurityMetadata ?? false;
  const requestBody = await applyFetchArgs(args, {
    objectSet: {
      type: "static",
      objects: rids
    },
    select: args?.$select ?? [],
    excludeRid: !args?.$includeRid,
    snapshot: useSnapshot,
    loadPropertySecurities: shouldLoadPropertySecurities
  }, client, {
    type: "object",
    apiName: ""
  });
  if (client.flushEdits != null) {
    await client.flushEdits();
  }
  const result = await OntologiesV2.OntologyObjectSets.loadMultipleObjectTypes(addUserAgentAndRequestContextHeaders(client, {
    osdkMetadata: undefined
  }), await client.ontologyRid, requestBody, {
    preview: true,
    transactionId: client.transactionId
  });
  return Promise.resolve({
    data: await client.objectFactory2(client, result.data, undefined, {}, shouldLoadPropertySecurities ? result.propertySecurities : undefined, !args.$includeRid, args.$select, false, result.interfaceToObjectTypeMappings, result.interfaceToObjectTypeMappingsV2),
    nextPageToken: result.nextPageToken,
    totalCount: result.totalCount
  });
}
async function fetchInterfacePage(client, interfaceType, args, objectSet, useSnapshot = false) {
  if (args.$__UNSTABLE_useOldInterfaceApis) {
    !(args.$loadPropertySecurityMetadata === false || args.$loadPropertySecurityMetadata === undefined) ? process.env.NODE_ENV !== "production" ? invariant(false, "`$loadPropertySecurityMetadata` is not supported with old interface APIs") : invariant(false) : void 0;
    const baseRequestBody = {
      augmentedProperties: {},
      augmentedSharedPropertyTypes: {},
      augmentedInterfacePropertyTypes: {},
      otherInterfaceTypes: [],
      selectedObjectTypes: [],
      selectedSharedPropertyTypes: args.$select ? [...args.$select] : [],
      selectedInterfacePropertyTypes: [],
      where: objectSetToSearchJsonV2(objectSet, interfaceType.apiName)
    };
    const requestBody = await applyFetchArgs(args, baseRequestBody, client, interfaceType);
    if (requestBody.selectedSharedPropertyTypes.length > 0) {
      const remapped = remapPropertyNames(interfaceType, requestBody.selectedSharedPropertyTypes);
      requestBody.selectedSharedPropertyTypes = Array.from(remapped);
    }
    if (client.flushEdits != null) {
      await client.flushEdits();
    }
    const result = await OntologiesV2.OntologyInterfaces.search(addUserAgentAndRequestContextHeaders(client, interfaceType), await client.ontologyRid, interfaceType.apiName, requestBody, {
      preview: true
    });
    result.data = await client.objectFactory(client, result.data,
    // drop readonly
    interfaceType.apiName, !args.$includeRid, await extractRdpDefinition(client, objectSet), undefined);
    return result;
  }
  const extractedInterfaceTypeApiName = (await extractObjectOrInterfaceType(client, objectSet))?.apiName ?? interfaceType.apiName;
  const resolvedInterfaceObjectSet = resolveInterfaceObjectSet(objectSet, extractedInterfaceTypeApiName, args);
  const shouldLoadPropertySecurities = args.$loadPropertySecurityMetadata ?? false;
  const requestBody = await buildAndRemapRequestBody(args, {
    objectSet: resolvedInterfaceObjectSet,
    select: args?.$select ? [...args.$select] : [],
    selectV2: [],
    loadPropertySecurities: shouldLoadPropertySecurities,
    excludeRid: !args?.$includeRid,
    snapshot: useSnapshot
  }, client, interfaceType);
  if (client.flushEdits != null) {
    await client.flushEdits();
  }
  const result = await OntologiesV2.OntologyObjectSets.loadMultipleObjectTypes(addUserAgentAndRequestContextHeaders(client, interfaceType), await client.ontologyRid, requestBody, {
    preview: true,
    branch: client.branch,
    transactionId: client.transactionId
  });
  return Promise.resolve({
    data: await client.objectFactory2(client, result.data, extractedInterfaceTypeApiName, {}, shouldLoadPropertySecurities ? result.propertySecurities : undefined, !args.$includeRid, args.$select, false, result.interfaceToObjectTypeMappings, result.interfaceToObjectTypeMappingsV2),
    nextPageToken: result.nextPageToken,
    totalCount: result.totalCount
  });
}

/** @internal */
export async function fetchPageInternal(client, objectType, objectSet, args = {}, useSnapshot = false) {
  if (objectType.type === "interface") {
    return await fetchInterfacePage(client, objectType, args, objectSet, useSnapshot); // fixme
  } else {
    return await fetchObjectPage(client, objectType, args, objectSet, useSnapshot); // fixme
  }
}

/** @internal */
export async function fetchPageWithErrorsInternal(client, objectType, objectSet, args = {}) {
  try {
    const result = await fetchPageInternal(client, objectType, objectSet, args);
    return {
      value: result
    };
  } catch (e) {
    if (e instanceof Error) {
      return {
        error: e
      };
    }
    return {
      error: e
    };
  }
}

/**
 * @param client
 * @param objectType
 * @param args
 * @param objectSet
 * @returns
 * @internal
 */
export async function fetchPage(client, objectType, args, objectSet = resolveBaseObjectSetType(objectType)) {
  return fetchPageInternal(client, objectType, objectSet, args);
}

/** @internal */
export async function fetchPageWithErrors(client, objectType, args, objectSet = resolveBaseObjectSetType(objectType)) {
  return fetchPageWithErrorsInternal(client, objectType, objectSet, args);
}

/** @internal */
async function buildAndRemapRequestBody(args, baseBody, client, objectType) {
  const requestBody = await applyFetchArgs(args, baseBody, client, objectType);
  if (requestBody.select != null && requestBody.select.length > 0) {
    const remapped = remapPropertyNames(objectType, requestBody.select);
    return {
      ...requestBody,
      select: remapped
    };
  }
  return requestBody;
}

/** @internal */
export function remapPropertyNames(objectOrInterface, propertyNames) {
  if (objectOrInterface == null) {
    return propertyNames;
  }
  if (objectOrInterface.type === "interface") {
    const [objApiNamespace] = extractNamespace(objectOrInterface.apiName);
    return propertyNames.map(name => {
      const [fieldApiNamespace, fieldShortName] = extractNamespace(name);
      return fieldApiNamespace == null && objApiNamespace != null ? `${objApiNamespace}.${fieldShortName}` : name;
    });
  }
  return propertyNames;
}
async function applyFetchArgs(args, body, _client, objectType) {
  if (args?.$nextPageToken) {
    body.pageToken = args.$nextPageToken;
  }
  if (args?.$pageSize != null) {
    body.pageSize = args.$pageSize;
  }
  if (args?.$loadPropertySecurityMetadata) {
    body.loadPropertySecurities = true;
  }
  const orderBy = args?.$orderBy;
  if (orderBy) {
    if (orderBy === "relevance") {
      body.orderBy = {
        orderType: "relevance",
        fields: []
      };
    } else {
      const orderByEntries = Object.entries(orderBy);
      const fieldNames = orderByEntries.map(([field]) => field);
      const remappedFields = remapPropertyNames(objectType, fieldNames);
      body.orderBy = {
        fields: orderByEntries.map(([, direction], index) => ({
          field: remappedFields[index],
          direction
        }))
      };
    }
  }
  return body;
}

/** @internal */
export async function fetchObjectPage(client, objectType, args, objectSet, useSnapshot = false) {
  // For simple object fetches, since we know the object type up front
  // we can parallelize network requests for loading metadata and loading the actual objects
  // In our object factory we await and block on loading the metadata, which if this call finishes, should already be cached on the client
  // We have an empty catch here so that if this call errors before we await later, we won't have an unhandled promise rejection that would crash the process
  // Swallowing the error is ok because we await the metadata load in the objectFactory later anyways which eventually bubbles up the error to the user
  void client.ontologyProvider.getObjectDefinition(objectType.apiName).catch(() => {});
  const shouldLoadPropertySecurities = args.$loadPropertySecurityMetadata ?? false;
  const requestBody = await buildAndRemapRequestBody(args, {
    objectSet,
    select: args?.$select ? [...args.$select] : [],
    selectV2: [],
    loadPropertySecurities: shouldLoadPropertySecurities,
    excludeRid: !args?.$includeRid,
    snapshot: useSnapshot
  }, client, objectType);
  if (client.flushEdits != null) {
    await client.flushEdits();
  }
  const r = await OntologiesV2.OntologyObjectSets.load(addUserAgentAndRequestContextHeaders(client, objectType), await client.ontologyRid, requestBody, {
    branch: client.branch,
    transactionId: client.transactionId
  });
  return Promise.resolve({
    data: await client.objectFactory(client, r.data, undefined, undefined, await extractRdpDefinition(client, objectSet), shouldLoadPropertySecurities ? r.propertySecurities : undefined, args.$select, false),
    nextPageToken: r.nextPageToken,
    totalCount: r.totalCount
  });
}
//# sourceMappingURL=fetchPage.js.map