import { parseEntityRef, stringifyLocationRef, stringifyEntityRef } from '@backstage/catalog-model';
import { ResponseError } from '@backstage/errors';
import crossFetch from 'cross-fetch';
import * as parser from 'uri-template';

const CATALOG_FILTER_EXISTS = Symbol.for(
  // Random UUID to ensure no collisions
  "CATALOG_FILTER_EXISTS_0e15b590c0b343a2bae3e787e84c2111"
);

function isQueryEntitiesInitialRequest(request) {
  return !request.cursor;
}

const pluginId = "catalog";

var __defProp$1 = Object.defineProperty;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$1 = (obj, key, value) => {
  __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
class DefaultApiClient {
  constructor(options) {
    __publicField$1(this, "discoveryApi");
    __publicField$1(this, "fetchApi");
    this.discoveryApi = options.discoveryApi;
    this.fetchApi = options.fetchApi || { fetch: crossFetch };
  }
  /**
   * Validate a given location.
   * @param analyzeLocationRequest
   */
  async analyzeLocation(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/analyze-location`;
    const uri = parser.parse(uriTemplate).expand({});
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "POST",
      body: JSON.stringify(request.body)
    });
  }
  /**
   * Create a location for a given target.
   * @param createLocationRequest
   * @param dryRun
   */
  async createLocation(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/locations{?dryRun}`;
    const uri = parser.parse(uriTemplate).expand({
      ...request.query
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "POST",
      body: JSON.stringify(request.body)
    });
  }
  /**
   * Delete a single entity by UID.
   * @param uid
   */
  async deleteEntityByUid(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-uid/{uid}`;
    const uri = parser.parse(uriTemplate).expand({
      uid: request.path.uid
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "DELETE"
    });
  }
  /**
   * Delete a location by id.
   * @param id
   */
  async deleteLocation(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/locations/{id}`;
    const uri = parser.parse(uriTemplate).expand({
      id: request.path.id
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "DELETE"
    });
  }
  /**
   * Get all entities matching a given filter.
   * @param fields Restrict to just these fields in the response.
   * @param limit Number of records to return in the response.
   * @param filter Filter for just the entities defined by this filter.
   * @param offset Number of records to skip in the query page.
   * @param after Pointer to the previous page of results.
   * @param order
   */
  async getEntities(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities{?fields,limit,filter*,offset,after,order*}`;
    const uri = parser.parse(uriTemplate).expand({
      ...request.query
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Search for entities by a given query.
   * @param fields Restrict to just these fields in the response.
   * @param limit Number of records to return in the response.
   * @param orderField The fields to sort returned results by.
   * @param cursor Cursor to a set page of results.
   * @param filter Filter for just the entities defined by this filter.
   * @param fullTextFilterTerm Text search term.
   * @param fullTextFilterFields A comma separated list of fields to sort returned results by.
   */
  async getEntitiesByQuery(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-query{?fields,limit,orderField*,cursor,filter*,fullTextFilterTerm,fullTextFilterFields}`;
    const uri = parser.parse(uriTemplate).expand({
      ...request.query
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get a batch set of entities given an array of entityRefs.
   * @param getEntitiesByRefsRequest
   */
  async getEntitiesByRefs(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-refs`;
    const uri = parser.parse(uriTemplate).expand({});
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "POST",
      body: JSON.stringify(request.body)
    });
  }
  /**
   * Get an entity's ancestry by entity ref.
   * @param kind
   * @param namespace
   * @param name
   */
  async getEntityAncestryByName(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}/ancestry`;
    const uri = parser.parse(uriTemplate).expand({
      kind: request.path.kind,
      namespace: request.path.namespace,
      name: request.path.name
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get an entity by an entity ref.
   * @param kind
   * @param namespace
   * @param name
   */
  async getEntityByName(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}`;
    const uri = parser.parse(uriTemplate).expand({
      kind: request.path.kind,
      namespace: request.path.namespace,
      name: request.path.name
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get a single entity by the UID.
   * @param uid
   */
  async getEntityByUid(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entities/by-uid/{uid}`;
    const uri = parser.parse(uriTemplate).expand({
      uid: request.path.uid
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get all entity facets that match the given filters.
   * @param facet
   * @param filter Filter for just the entities defined by this filter.
   */
  async getEntityFacets(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/entity-facets{?facet*,filter*}`;
    const uri = parser.parse(uriTemplate).expand({
      ...request.query
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get a location by id.
   * @param id
   */
  async getLocation(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/locations/{id}`;
    const uri = parser.parse(uriTemplate).expand({
      id: request.path.id
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get a location for entity.
   * @param kind
   * @param namespace
   * @param name
   */
  async getLocationByEntity(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/locations/by-entity/{kind}/{namespace}/{name}`;
    const uri = parser.parse(uriTemplate).expand({
      kind: request.path.kind,
      namespace: request.path.namespace,
      name: request.path.name
    });
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Get all locations
   */
  async getLocations(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/locations`;
    const uri = parser.parse(uriTemplate).expand({});
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "GET"
    });
  }
  /**
   * Refresh the entity related to entityRef.
   * @param refreshEntityRequest
   */
  async refreshEntity(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/refresh`;
    const uri = parser.parse(uriTemplate).expand({});
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "POST",
      body: JSON.stringify(request.body)
    });
  }
  /**
   * Validate that a passed in entity has no errors in schema.
   * @param validateEntityRequest
   */
  async validateEntity(request, options) {
    const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
    const uriTemplate = `/validate-entity`;
    const uri = parser.parse(uriTemplate).expand({});
    return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
      headers: {
        "Content-Type": "application/json",
        ...(options == null ? void 0 : options.token) && { Authorization: `Bearer ${options == null ? void 0 : options.token}` }
      },
      method: "POST",
      body: JSON.stringify(request.body)
    });
  }
}

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};
class CatalogClient {
  constructor(options) {
    __publicField(this, "apiClient");
    this.apiClient = new DefaultApiClient(options);
  }
  /**
   * {@inheritdoc CatalogApi.getEntityAncestors}
   */
  async getEntityAncestors(request, options) {
    return await this.requestRequired(
      await this.apiClient.getEntityAncestryByName(
        { path: parseEntityRef(request.entityRef) },
        options
      )
    );
  }
  /**
   * {@inheritdoc CatalogApi.getLocationById}
   */
  async getLocationById(id, options) {
    return await this.requestOptional(
      await this.apiClient.getLocation({ path: { id } }, options)
    );
  }
  /**
   * {@inheritdoc CatalogApi.getLocationByEntity}
   */
  async getLocationByEntity(entityRef, options) {
    return await this.requestOptional(
      await this.apiClient.getLocationByEntity(
        { path: parseEntityRef(entityRef) },
        options
      )
    );
  }
  /**
   * {@inheritdoc CatalogApi.getEntities}
   */
  async getEntities(request, options) {
    const {
      filter = [],
      fields = [],
      order,
      offset,
      limit,
      after
    } = request != null ? request : {};
    const encodedOrder = [];
    if (order) {
      for (const directive of [order].flat()) {
        if (directive) {
          encodedOrder.push(`${directive.order}:${directive.field}`);
        }
      }
    }
    const entities = await this.requestRequired(
      await this.apiClient.getEntities(
        {
          query: {
            fields,
            limit,
            filter: this.getFilterValue(filter),
            offset,
            after,
            order: order ? encodedOrder : void 0
          }
        },
        options
      )
    );
    const refCompare = (a, b) => {
      var _a, _b;
      if (((_a = a.metadata) == null ? void 0 : _a.name) === void 0 || a.kind === void 0 || ((_b = b.metadata) == null ? void 0 : _b.name) === void 0 || b.kind === void 0) {
        return 0;
      }
      const aRef = stringifyEntityRef(a);
      const bRef = stringifyEntityRef(b);
      if (aRef < bRef) {
        return -1;
      }
      if (aRef > bRef) {
        return 1;
      }
      return 0;
    };
    return { items: entities.sort(refCompare) };
  }
  /**
   * {@inheritdoc CatalogApi.getEntitiesByRefs}
   */
  async getEntitiesByRefs(request, options) {
    const response = await this.apiClient.getEntitiesByRefs(
      {
        body: request
      },
      options
    );
    if (!response.ok) {
      throw await ResponseError.fromResponse(response);
    }
    const { items } = await response.json();
    return { items: items.map((i) => i != null ? i : void 0) };
  }
  /**
   * {@inheritdoc CatalogApi.queryEntities}
   */
  async queryEntities(request = {}, options) {
    var _a, _b;
    const params = {};
    if (isQueryEntitiesInitialRequest(request)) {
      const {
        fields = [],
        filter,
        limit,
        orderFields,
        fullTextFilter
      } = request;
      params.filter = this.getFilterValue(filter);
      if (limit !== void 0) {
        params.limit = limit;
      }
      if (orderFields !== void 0) {
        params.orderField = (Array.isArray(orderFields) ? orderFields : [orderFields]).map(({ field, order }) => `${field},${order}`);
      }
      if (fields.length) {
        params.fields = fields;
      }
      const normalizedFullTextFilterTerm = (_a = fullTextFilter == null ? void 0 : fullTextFilter.term) == null ? void 0 : _a.trim();
      if (normalizedFullTextFilterTerm) {
        params.fullTextFilterTerm = normalizedFullTextFilterTerm;
      }
      if ((_b = fullTextFilter == null ? void 0 : fullTextFilter.fields) == null ? void 0 : _b.length) {
        params.fullTextFilterFields = fullTextFilter.fields;
      }
    } else {
      const { fields = [], limit, cursor } = request;
      params.cursor = cursor;
      if (limit !== void 0) {
        params.limit = limit;
      }
      if (fields.length) {
        params.fields = fields;
      }
    }
    return this.apiClient.getEntitiesByQuery({ query: params }, options).then((r) => r.json());
  }
  /**
   * {@inheritdoc CatalogApi.getEntityByRef}
   */
  async getEntityByRef(entityRef, options) {
    return this.requestOptional(
      await this.apiClient.getEntityByName(
        {
          path: parseEntityRef(entityRef)
        },
        options
      )
    );
  }
  // NOTE(freben): When we deprecate getEntityByName from the interface, we may
  // still want to leave this implementation in place for quite some time
  // longer, to minimize the risk for breakages. Suggested date for removal:
  // August 2022
  /**
   * @deprecated Use getEntityByRef instead
   */
  async getEntityByName(compoundName, options) {
    const { kind, namespace = "default", name } = compoundName;
    return this.requestOptional(
      await this.apiClient.getEntityByName(
        { path: { kind, namespace, name } },
        options
      )
    );
  }
  /**
   * {@inheritdoc CatalogApi.refreshEntity}
   */
  async refreshEntity(entityRef, options) {
    const response = await this.apiClient.refreshEntity(
      { body: { entityRef } },
      options
    );
    if (response.status !== 200) {
      throw new Error(await response.text());
    }
  }
  /**
   * {@inheritdoc CatalogApi.getEntityFacets}
   */
  async getEntityFacets(request, options) {
    const { filter = [], facets } = request;
    return await this.requestOptional(
      await this.apiClient.getEntityFacets(
        {
          query: { facet: facets, filter: this.getFilterValue(filter) }
        },
        options
      )
    );
  }
  /**
   * {@inheritdoc CatalogApi.addLocation}
   */
  async addLocation(request, options) {
    const { type = "url", target, dryRun } = request;
    const response = await this.apiClient.createLocation(
      {
        body: { type, target },
        query: { dryRun: dryRun ? "true" : void 0 }
      },
      options
    );
    if (response.status !== 201) {
      throw new Error(await response.text());
    }
    const { location, entities, exists } = await response.json();
    if (!location) {
      throw new Error(`Location wasn't added: ${target}`);
    }
    return {
      location,
      entities,
      exists
    };
  }
  /**
   * {@inheritdoc CatalogApi.getLocationByRef}
   */
  async getLocationByRef(locationRef, options) {
    const all = await this.requestRequired(
      await this.apiClient.getLocations({}, options)
    );
    return all.map((r) => r.data).find((l) => locationRef === stringifyLocationRef(l));
  }
  /**
   * {@inheritdoc CatalogApi.removeLocationById}
   */
  async removeLocationById(id, options) {
    await this.requestIgnored(
      await this.apiClient.deleteLocation({ path: { id } }, options)
    );
  }
  /**
   * {@inheritdoc CatalogApi.removeEntityByUid}
   */
  async removeEntityByUid(uid, options) {
    await this.requestIgnored(
      await this.apiClient.deleteEntityByUid({ path: { uid } }, options)
    );
  }
  /**
   * {@inheritdoc CatalogApi.validateEntity}
   */
  async validateEntity(entity, locationRef, options) {
    const response = await this.apiClient.validateEntity(
      { body: { entity, location: locationRef } },
      options
    );
    if (response.ok) {
      return {
        valid: true
      };
    }
    if (response.status !== 400) {
      throw await ResponseError.fromResponse(response);
    }
    const { errors = [] } = await response.json();
    return {
      valid: false,
      errors
    };
  }
  //
  // Private methods
  //
  async requestIgnored(response) {
    if (!response.ok) {
      throw await ResponseError.fromResponse(response);
    }
  }
  async requestRequired(response) {
    if (!response.ok) {
      throw await ResponseError.fromResponse(response);
    }
    return response.json();
  }
  async requestOptional(response) {
    if (!response.ok) {
      if (response.status === 404) {
        return void 0;
      }
      throw await ResponseError.fromResponse(response);
    }
    return await response.json();
  }
  getFilterValue(filter = []) {
    const filters = [];
    for (const filterItem of [filter].flat()) {
      const filterParts = [];
      for (const [key, value] of Object.entries(filterItem)) {
        for (const v of [value].flat()) {
          if (v === CATALOG_FILTER_EXISTS) {
            filterParts.push(key);
          } else if (typeof v === "string") {
            filterParts.push(`${key}=${v}`);
          }
        }
      }
      if (filterParts.length) {
        filters.push(filterParts.join(","));
      }
    }
    return filters;
  }
}

const ENTITY_STATUS_CATALOG_PROCESSING_TYPE = "backstage.io/catalog-processing";

export { CATALOG_FILTER_EXISTS, CatalogClient, ENTITY_STATUS_CATALOG_PROCESSING_TYPE };
//# sourceMappingURL=index.esm.js.map
