/*
 * Copyright 2024 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 invariant from "tiny-invariant";
import { GeotimeSeriesPropertyImpl } from "../../createGeotimeSeriesProperty.js";
import { MediaReferencePropertyImpl } from "../../createMediaReferenceProperty.js";
import { TimeSeriesPropertyImpl } from "../../createTimeseriesProperty.js";
import { hydrateAttachmentFromRidInternal } from "../../public-utils/hydrateAttachmentFromRid.js";
import { createObjectSpecifierFromPrimaryKey } from "../../util/objectSpecifierUtils.js";
import { applyPropertyFormatter } from "../formatting/applyPropertyFormatter.js";
import { get$as } from "./getDollarAs.js";
import { get$link } from "./getDollarLink.js";
import { ClientRef, ObjectDefRef, PropertySecuritiesRef, UnderlyingOsdkObject } from "./InternalSymbols.js";
const specialPropertyTypes = new Set(["attachment", "geotimeSeriesReference", "mediaReference", "numericTimeseries", "stringTimeseries", "sensorTimeseries"]);

// kept separate so we are not redefining these functions
// every time an object is created.
const basePropDefs = {
  "$as": {
    get: function () {
      return get$as(this[ObjectDefRef]);
    }
  },
  "$link": {
    get: function () {
      return get$link(this);
    }
  },
  "$clone": {
    value: function (update) {
      // I think `rawObj` is the same thing as `this` and can be removed?
      const rawObj = this[UnderlyingOsdkObject];
      const def = this[ObjectDefRef];
      if (update == null) {
        return createOsdkObject(this[ClientRef], def, {
          ...rawObj
        });
      }
      if (def.primaryKeyApiName in update && rawObj[def.primaryKeyApiName] !== update[def.primaryKeyApiName]) {
        throw new Error(`Cannot update ${def.apiName} object with differing primary key values `);
      }
      if (def.titleProperty in update && !("$title" in update)) {
        update.$title = update[def.titleProperty];
      }
      const newObject = {
        ...this[UnderlyingOsdkObject],
        ...update
      };
      return createOsdkObject(this[ClientRef], this[ObjectDefRef], newObject);
    }
  },
  "$objectSpecifier": {
    get: function () {
      const rawObj = this[UnderlyingOsdkObject];
      return createObjectSpecifierFromPrimaryKey(this[ObjectDefRef], rawObj.$primaryKey);
    },
    enumerable: true
  },
  "$propertySecurities": {
    get: function () {
      return this[PropertySecuritiesRef];
    },
    enumerable: true
  },
  "$__EXPERIMENTAL__NOT_SUPPORTED_YET__metadata": {
    get: function () {
      return {
        ObjectMetadata: this[ObjectDefRef]
      };
    },
    enumerable: false
  },
  "$__EXPERIMENTAL__NOT_SUPPORTED_YET__getFormattedValue": {
    value: function (propertyApiName, options) {
      const rawObj = this[UnderlyingOsdkObject];
      const def = this[ObjectDefRef];
      const propertyValue = rawObj[propertyApiName];
      return applyPropertyFormatter(propertyValue, def.properties[propertyApiName], rawObj, options);
    },
    enumerable: false
  }
};

/**
 * @internal
 * @param client
 * @param objectDef
 * @param simpleOsdkProperties
 */
export function createOsdkObject(client, objectDef, simpleOsdkProperties, derivedPropertyTypeByName = {}, wirePropertySecurities = []) {
  const {
    parsedObject,
    clientPropertySecurities
  } = parseWhenSecuritiesLoaded(wirePropertySecurities, simpleOsdkProperties, objectDef, derivedPropertyTypeByName);

  // updates the object's "hidden class/map".
  const rawObj = parsedObject;
  Object.defineProperties(rawObj, {
    [UnderlyingOsdkObject]: {
      enumerable: false,
      value: simpleOsdkProperties
    },
    [PropertySecuritiesRef]: {
      enumerable: false,
      value: clientPropertySecurities
    },
    [ObjectDefRef]: {
      value: objectDef,
      enumerable: false
    },
    // TODO: Potentially update when GA metadata field
    [ClientRef]: {
      value: client,
      enumerable: false
    },
    ...basePropDefs
  });

  // Assign the special values
  for (const propKey of Object.keys(rawObj)) {
    if (propKey in objectDef.properties && typeof objectDef.properties[propKey].type === "string" && specialPropertyTypes.has(objectDef.properties[propKey].type)) {
      rawObj[propKey] = createSpecialProperty(client, objectDef, rawObj, propKey);
    } else if (propKey in derivedPropertyTypeByName) {
      rawObj[propKey] = modifyRdpProperties(client, derivedPropertyTypeByName, rawObj[propKey], propKey);
    }
  }
  return Object.freeze(rawObj);
}
function modifyRdpProperties(client, derivedPropertyTypeByName, rawValue, propKey) {
  if (derivedPropertyTypeByName[propKey].definition.type === "selection" && derivedPropertyTypeByName[propKey].definition.operation.type === "count") {
    const num = Number(rawValue);
    !Number.isSafeInteger(num) ? process.env.NODE_ENV !== "production" ? invariant(false, "Count aggregation for derived property " + propKey + " returned a value larger than safe integer.") : invariant(false) : void 0;
    return num;
  } // Selected or collected properties need to be deserialized specially when constructed with RDP
  else if (derivedPropertyTypeByName[propKey].selectedOrCollectedPropertyType != null && typeof derivedPropertyTypeByName[propKey].selectedOrCollectedPropertyType.type === "string" && specialPropertyTypes.has(derivedPropertyTypeByName[propKey].selectedOrCollectedPropertyType.type)) {
    switch (derivedPropertyTypeByName[propKey].selectedOrCollectedPropertyType?.type) {
      case "attachment":
        if (Array.isArray(rawValue)) {
          return rawValue.map(a => hydrateAttachmentFromRidInternal(client, a.rid));
        } else {
          return hydrateAttachmentFromRidInternal(client, rawValue.rid);
        }
        break;
      default:
        process.env.NODE_ENV !== "production" ? invariant(false, "Derived property aggregations for Timeseries and Media are not supported") : invariant(false);
    }
  }
  return rawValue;
}
function createSpecialProperty(client, objectDef, rawObject, p) {
  const rawValue = rawObject[p];
  const propDef = objectDef.properties[p];
  if (process.env.NODE_ENV !== "production") {
    !(propDef != null && typeof propDef.type === "string" && specialPropertyTypes.has(propDef.type)) ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0;
  }
  if (propDef.type === "attachment") {
    if (Array.isArray(rawValue)) {
      return rawValue.map(a => hydrateAttachmentFromRidInternal(client, a.rid));
    }
    return hydrateAttachmentFromRidInternal(client, rawValue.rid);
  }
  if (propDef.type === "numericTimeseries" || propDef.type === "stringTimeseries" || propDef.type === "sensorTimeseries") {
    return new TimeSeriesPropertyImpl(client, objectDef.apiName, rawObject[objectDef.primaryKeyApiName], p);
  }
  if (propDef.type === "geotimeSeriesReference") {
    return new GeotimeSeriesPropertyImpl(client, objectDef.apiName, rawObject[objectDef.primaryKeyApiName], p, rawValue.type === "geotimeSeriesValue" ? {
      time: rawValue.timestamp,
      value: {
        type: "Point",
        coordinates: rawValue.position
      }
    } : undefined);
  }
  if (propDef.type === "mediaReference") {
    return new MediaReferencePropertyImpl({
      client,
      objectApiName: objectDef.apiName,
      primaryKey: rawObject[objectDef.primaryKeyApiName],
      propertyName: p,
      mediaReference: rawValue
    });
  }
}
function parseWhenSecuritiesLoaded(wirePropertySecurities, rawObject, objectDef, derivedPropertyTypeByName = {}) {
  if (wirePropertySecurities == null || wirePropertySecurities.length === 0) {
    return {
      parsedObject: rawObject,
      clientPropertySecurities: undefined
    };
  }
  const parsedObject = {
    ...rawObject
  };
  const clientPropertySecurities = {};
  for (const propKey of Object.keys(rawObject)) {
    if (propKey in objectDef.properties || propKey in derivedPropertyTypeByName) {
      const value = rawObject[propKey];
      if (Array.isArray(value)) {
        const newVal = [];
        const newSecurities = [];
        value.forEach(spv => {
          !(typeof spv === "object" && spv != null && "value" in spv && "propertySecurityIndex" in spv) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected destructured secured property value object in array") : invariant(false) : void 0;
          const securedValue = spv;
          newVal.push(securedValue.value);
          const securityIndex = securedValue.propertySecurityIndex;
          !(securityIndex != null) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected property security index to be defined") : invariant(false) : void 0;
          !(securityIndex < wirePropertySecurities.length) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected property security index to be within bounds") : invariant(false) : void 0;
          newSecurities.push(wirePropertySecurities[securityIndex].disjunction.map(wireToClientPropertySecurities));
        });
        parsedObject[propKey] = newVal;
        clientPropertySecurities[propKey] = newSecurities;
      } // Check if this is a secured property value object
      else if (typeof value === "object" && value != null && "value" in value && "propertySecurityIndex" in value) {
        const securedValue = value;
        parsedObject[propKey] = securedValue.value;
        const securityIndex = securedValue.propertySecurityIndex;
        !(securityIndex != null) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected property security index to be defined") : invariant(false) : void 0;
        !(securityIndex < wirePropertySecurities.length) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected property security index to be within bounds") : invariant(false) : void 0;
        clientPropertySecurities[propKey] = wirePropertySecurities[securityIndex].disjunction.map(wireToClientPropertySecurities);
      } else {
        // Regular property without security
        parsedObject[propKey] = value;
      }
    }
  }
  return {
    parsedObject,
    clientPropertySecurities
  };
}
function wireToClientPropertySecurities(propertySecurity) {
  switch (propertySecurity.type) {
    case "propertyMarkingSummary":
      return {
        type: "propertyMarkings",
        conjunctive: propertySecurity.conjunctive,
        containerConjunctive: propertySecurity.containerConjunctive,
        disjunctive: propertySecurity.disjunctive,
        containerDisjunctive: propertySecurity.containerDisjunctive
      };
    case "errorComputingSecurity":
      return {
        type: "errorComputingSecurity"
      };
    case "unsupportedPolicy":
      return {
        type: "unsupportedPolicy"
      };
  }
}
//# sourceMappingURL=createOsdkObject.js.map