packages feed

bloodhound-1.0.0.0: src/Database/Bloodhound/ElasticSearch8/Requests.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}

module Database.Bloodhound.ElasticSearch8.Requests
  ( module Reexport,
    esql,
    esqlWith,
    esqlAsync,
    esqlAsyncWith,
    getAsyncESQL,
    getAsyncESQLWith,
    deleteAsyncESQL,
    stopAsyncESQL,
    putInferenceEndpoint,
    putInferenceEndpointWith,
    updateInferenceEndpoint,
    getInferenceEndpoints,
    deleteInferenceEndpoint,
    deleteInferenceEndpointWith,
    runInference,
    runInferenceWith,
    streamCompletionInference,
    streamCompletionInferenceWith,
    streamChatCompletionInference,
    streamChatCompletionInferenceWith,
    putQueryRuleset,
    getQueryRuleset,
    deleteQueryRuleset,
    testQueryRuleset,
    putSearchApplication,
    putSearchApplicationWith,
    getSearchApplication,
    deleteSearchApplication,
    searchApplication,
    searchApplicationWith,
    listSearchApplications,
    listSearchApplicationsWith,
    renderSearchApplicationQuery,
    putDataStreamLifecycle,
    putDataStreamsLifecycle,
    getDataStreamLifecycle,
    deleteDataStreamLifecycle,
    getDataStreamOptions,
    updateDataStreamOptions,
    deleteDataStreamOptions,
    getDataStreamLifecycleStats,
    explainDataStreamLifecycle,
    explainDataStreamLifecycleWith,
    eqlSearch,
    eqlSearchWith,
    getEQLSearch,
    getEQLStatus,
    deleteEQLSearch,
    getTermsEnum,
    getTermsEnumWith,
    putSynonymsSet,
    getSynonymsSet,
    getSynonymsSetWith,
    deleteSynonymsSet,
    getAnalyticsCollections,
    putAnalyticsCollection,
    deleteAnalyticsCollection,
    postAnalyticsEvent,
    postAnalyticsEventWith,
    createConnector,
    putConnector,
    getConnector,
    deleteConnector,
    deleteConnectorWith,
    listConnectors,
    listConnectorsWith,
    checkInConnector,
    updateConnectorApiKeyId,
    updateConnectorConfiguration,
    updateConnectorError,
    updateConnectorFeatures,
    updateConnectorFiltering,
    updateConnectorDraftFilteringValidation,
    activateConnectorDraftFiltering,
    updateConnectorIndexName,
    updateConnectorNameDescription,
    updateConnectorNative,
    updateConnectorPipeline,
    updateConnectorScheduling,
    updateConnectorServiceType,
    updateConnectorStatus,
    createConnectorSyncJob,
    getConnectorSyncJob,
    listConnectorSyncJobs,
    listConnectorSyncJobsWith,
    deleteConnectorSyncJob,
    cancelConnectorSyncJob,
    checkInConnectorSyncJob,
    claimConnectorSyncJob,
    setConnectorSyncJobError,
    setConnectorSyncJobStats,
    migrateReindex,
    cancelMigrateReindex,
    getMigrateReindexStatus,
    createIndexFrom,
    createIndexFromWith,
  )
where

import Data.Aeson (FromJSON, Value, encode)
import Data.ByteString.Lazy qualified as L
import Data.List.NonEmpty (NonEmpty, toList)
import Data.Maybe (catMaybes)
import Data.Text (Text)
import Data.Text qualified as T
import Database.Bloodhound.Client.Cluster
import Database.Bloodhound.Common.Requests as Reexport
import Database.Bloodhound.ElasticSearch7.Requests qualified as Requests7
import Database.Bloodhound.ElasticSearch8.Types
import Database.Bloodhound.Internal.Utils.Requests

-- | 'esql' builds the 'BHRequest' for the ES|QL @POST /_query@ endpoint
-- (Elasticsearch 8.11+). The body is the encoded 'ESQLRequest'; the response
-- is parsed into 'ESQLResult'.
--
-- Equivalent to @'esqlWith' req 'defaultESQLQueryOptions'@ — emits no
-- query string.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
esql ::
  ESQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse ESQLResult)
esql = flip esqlWith defaultESQLQueryOptions

-- | 'esqlWith' builds the 'BHRequest' for the ES|QL @POST /_query@
-- endpoint (Elasticsearch 8.11+) with the documented URI query
-- parameters ('ESQLQueryOptions'). The body is the encoded
-- 'ESQLRequest'; the response is parsed into 'ESQLResult'.
--
-- Pass 'defaultESQLQueryOptions' to emit no query string (identical
-- wire shape to 'esql').
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
esqlWith ::
  ESQLRequest ->
  ESQLQueryOptions ->
  BHRequest StatusDependant (ParsedEsResponse ESQLResult)
esqlWith req opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode req)
  where
    endpoint =
      mkEndpoint ["_query"]
        `withQueries` esqlOptionsParams opts

-- | 'esqlAsync' builds the 'BHRequest' for the async ES|QL
-- @POST /_query\/async@ endpoint (Elasticsearch 8.11+, identical wire
-- format in ES9). The body is the encoded 'AsyncESQLRequest' — unlike
-- async search, ES|QL async carries @wait_for_completion_timeout@,
-- @keep_alive@, @keep_on_completion@, etc. as JSON body fields, /not/
-- as query parameters.
--
-- Equivalent to @'esqlAsyncWith' req 'defaultESQLQueryOptions'@ — emits
-- no query string.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
esqlAsync ::
  AsyncESQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse AsyncESQLResult)
esqlAsync = flip esqlAsyncWith defaultESQLQueryOptions

-- | 'esqlAsyncWith' builds the 'BHRequest' for the async ES|QL
-- @POST /_query\/async@ endpoint (Elasticsearch 8.11+) with the
-- documented URI query parameters ('ESQLQueryOptions') — the same
-- parameter surface as the synchronous 'esqlWith', minus @format@
-- (see the note at 'ESQLQueryOptions'). The body is the encoded
-- 'AsyncESQLRequest'; the response is parsed into 'AsyncESQLResult'.
--
-- Pass 'defaultESQLQueryOptions' to emit no query string (identical
-- wire shape to 'esqlAsync').
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
esqlAsyncWith ::
  AsyncESQLRequest ->
  ESQLQueryOptions ->
  BHRequest StatusDependant (ParsedEsResponse AsyncESQLResult)
esqlAsyncWith req opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode req)
  where
    endpoint =
      mkEndpoint ["_query", "async"]
        `withQueries` esqlOptionsParams opts

-- | 'getAsyncESQL' builds the 'BHRequest' for
-- @GET /_query\/async\/{id}@ — polling the state and (partial or final)
-- results of an async ES|QL query. The optional 'Text' sets
-- @wait_for_completion_timeout@ as a query parameter (duration string
-- such as @"5s"@); 'Nothing' uses the server default (return immediately
-- with the current state).
--
-- This is the common-case convenience for the single most-used
-- parameter; for the full polling parameter surface (@keep_alive@,
-- @drop_null_columns@) see 'getAsyncESQLWith'.
--
-- Equivalent to
-- @'getAsyncESQLWith' id ('defaultGetAsyncESQLOptions' { 'gaesqloWaitForCompletionTimeout' = mTimeout })@.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
getAsyncESQL ::
  AsyncESQLId ->
  Maybe Text ->
  BHRequest StatusDependant (ParsedEsResponse AsyncESQLResult)
getAsyncESQL searchId mTimeout =
  getAsyncESQLWith
    searchId
    ( defaultGetAsyncESQLOptions
        { gaesqloWaitForCompletionTimeout = mTimeout
        }
    )

-- | 'getAsyncESQLWith' is the fully-parameterised form of
-- 'getAsyncESQL', exposing every URI parameter accepted by
-- @GET /_query\/async\/{id}@ via 'GetAsyncESQLOptions'
-- (@wait_for_completion_timeout@, @keep_alive@, @drop_null_columns@).
-- Pass 'defaultGetAsyncESQLOptions' to reproduce the legacy behaviour
-- (no query string).
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
getAsyncESQLWith ::
  AsyncESQLId ->
  GetAsyncESQLOptions ->
  BHRequest StatusDependant (ParsedEsResponse AsyncESQLResult)
getAsyncESQLWith (AsyncESQLId searchId) opts =
  withBHResponseParsedEsResponse $
    get @StatusDependant endpoint
  where
    endpoint =
      mkEndpoint ["_query", "async", searchId]
        `withQueries` getAsyncESQLOptionsParams opts

-- | 'deleteAsyncESQL' builds the 'BHRequest' for
-- @DELETE /_query\/async\/{id}@ — deleting an async ES|QL result and its
-- context. Returns 'Acknowledged' on success.
--
-- The endpoint is 'StatusDependant' — consistent with the other delete
-- functions in this module ('deleteInferenceEndpoint',
-- 'deleteQueryRuleset', 'deleteSearchApplication'): deleting a
-- non-existent or already-purged async id surfaces as an 'EsError'
-- (a @404@ with an error body); wrap with 'tryPerformBHRequest' for
-- miss-tolerant deletion.
--
-- Note: the analogous async-result delete 'deleteEQLSearch'
-- (@DELETE /_eql\/search\/{id}@) is still 'StatusIndependant' and on a
-- missing id yields only a generic @\"Unable to parse body\"@ 'EsError'
-- rather than the structured server error decoded here; aligning it is
-- out of scope.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
deleteAsyncESQL ::
  AsyncESQLId ->
  BHRequest StatusDependant Acknowledged
deleteAsyncESQL (AsyncESQLId searchId) =
  delete (mkEndpoint ["_query", "async", searchId])

-- | 'stopAsyncESQL' builds the 'BHRequest' for
-- @POST /_query\/async\/{id}\/stop@ — requesting the server to stop a
-- running async ES|QL query (Elasticsearch 8.11+). The response is the
-- full 'AsyncESQLResult' for the stopped query: @is_running@ flips to
-- @False@, and @columns@\/@values@ carry whatever the query produced
-- before being stopped.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/esql-rest.html>.
stopAsyncESQL ::
  AsyncESQLId ->
  BHRequest StatusDependant (ParsedEsResponse AsyncESQLResult)
stopAsyncESQL (AsyncESQLId searchId) =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint emptyBody
  where
    endpoint = mkEndpoint ["_query", "async", searchId, "stop"]

-- | 'putInferenceEndpoint' builds the 'BHRequest' for
-- @PUT \/_inference\/{task_type}\/{inference_id}@ (Elasticsearch 8.12+),
-- which creates or updates an inference endpoint for a provider such as
-- OpenAI, Cohere or ELSER. The same PUT path is used for both create and
-- full replacement; for a partial update use 'updateInferenceEndpoint'
-- (the dedicated @\/_update@ endpoint), which does not require re-sending
-- every required field.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>.
putInferenceEndpoint ::
  TaskType ->
  InferenceId ->
  InferenceConfig ->
  BHRequest StatusDependant (ParsedEsResponse InferencePutResponse)
putInferenceEndpoint taskType' inferenceId config =
  putInferenceEndpointWith taskType' inferenceId config defaultPutInferenceOptions

-- | 'putInferenceEndpointWith' is the fully-parameterised form of
-- 'putInferenceEndpoint'. 'PutInferenceOptions' carries the @timeout@
-- query parameter documented at
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>:
-- an upper bound on the time the server spends on the create\/update.
-- Passing 'defaultPutInferenceOptions' yields a request byte-for-byte
-- identical to 'putInferenceEndpoint'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>.
putInferenceEndpointWith ::
  TaskType ->
  InferenceId ->
  InferenceConfig ->
  PutInferenceOptions ->
  BHRequest StatusDependant (ParsedEsResponse InferencePutResponse)
putInferenceEndpointWith taskType' inferenceId config opts =
  withBHResponseParsedEsResponse $
    put @StatusDependant endpoint (encode config)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          taskTypeToText taskType',
          unInferenceId inferenceId
        ]
        `withQueries` putInferenceOptionsParams opts

-- | 'updateInferenceEndpoint' builds the 'BHRequest' for
-- @PUT \/_inference\/{task_type}\/{inference_id}\/_update@
-- (Elasticsearch 8.12+, @operation-inference-update@), which partially
-- updates an existing inference endpoint. The request body has the same
-- shape as 'InferenceConfig' (the @service@ + @service_settings@ +
-- @task_settings@ + @chunking_settings@ envelope) but the server treats
-- omitted fields as \"leave unchanged\" rather than requiring the full
-- create payload. The response is the updated endpoint as a bare
-- 'InferenceInfo' (the @InferenceEndpointInfo@ object, without the
-- @<task_type>\/<inference_id>@ key wrapper used by the list APIs).
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-update>.
updateInferenceEndpoint ::
  TaskType ->
  InferenceId ->
  InferenceConfig ->
  BHRequest StatusDependant (ParsedEsResponse InferenceInfo)
updateInferenceEndpoint taskType' inferenceId config =
  withBHResponseParsedEsResponse $
    put @StatusDependant endpoint (encode config)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          taskTypeToText taskType',
          unInferenceId inferenceId,
          "_update"
        ]

-- | 'deleteInferenceEndpoint' builds the 'BHRequest' for
-- @DELETE \/_inference\/{task_type}\/{inference_id}@ (Elasticsearch 8.12+),
-- which deletes a previously-configured inference endpoint. The endpoint is
-- 'StatusDependant': a @404@ for a missing inference endpoint surfaces as an
-- 'EsError'; wrap with 'tryPerformBHRequest' for miss-tolerant deletion.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/delete-inference-api.html>.
deleteInferenceEndpoint ::
  TaskType ->
  InferenceId ->
  BHRequest StatusDependant Acknowledged
deleteInferenceEndpoint taskType' inferenceId =
  delete endpoint
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          taskTypeToText taskType',
          unInferenceId inferenceId
        ]

-- | 'deleteInferenceEndpointWith' is the fully-parameterised form of
-- 'deleteInferenceEndpoint'. It attaches the @dry_run@ and @force@ query
-- parameters described by 'DeleteInferenceOptions':
--
-- * @dry_run=true@ previews the deletion (and the conflict checks) without
--   actually removing the endpoint.
-- * @force=true@ deletes the endpoint even when it is in use (referenced by
--   other configurations); without it the delete can fail.
--
-- 'deleteInferenceEndpoint' emits no query string at all; the two are
-- semantically equivalent when 'defaultDeleteInferenceOptions' is passed
-- (the server treats an absent flag as @false@).
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/delete-inference-api.html>.
deleteInferenceEndpointWith ::
  TaskType ->
  InferenceId ->
  DeleteInferenceOptions ->
  BHRequest StatusDependant Acknowledged
deleteInferenceEndpointWith taskType' inferenceId opts =
  delete (endpoint `withQueries` deleteInferenceOptionsParams opts)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          taskTypeToText taskType',
          unInferenceId inferenceId
        ]

-- | 'getInferenceEndpoints' builds the 'BHRequest' for the
-- @GET \/_inference@ family (Elasticsearch 8.12+), which lists configured
-- inference endpoints. The path narrows the response server-side:
--
-- * @'Nothing'@  task type  → @GET \/_inference@ returns every endpoint.
-- * @'Just' tt@, no id      → @GET \/_inference\/{task_type}\/_all@ returns
--   the endpoints of that task type.
-- * @'Just' tt@, @'Just' iid@ → @GET \/_inference\/{task_type}\/{inference_id}@
--   returns a single endpoint.
--
-- Passing @'Just' iid@ without a task type is not a valid Elasticsearch
-- URL; in that case the builder falls back to @GET \/_inference@ (lists
-- every endpoint) rather than synthesising an unsupported path.
--
-- The response is a JSON object keyed by @<task_type>\/<inference_id>@;
-- the 'FromJSON' instance of 'InferenceEndpointsResponse' splits each
-- key and flattens the map into an 'InferenceEndpointsResponse', which
-- 'getInferenceEndpoints' unwraps via 'unInferenceEndpointsResponse'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/get-inference-api.html>.
getInferenceEndpoints ::
  Maybe TaskType ->
  Maybe InferenceId ->
  BHRequest StatusDependant [InferenceInfo]
getInferenceEndpoints mTaskType mInferenceId =
  unInferenceEndpointsResponse <$> get @StatusDependant endpoint
  where
    endpoint = case (mTaskType, mInferenceId) of
      (Just tt, Just iid) ->
        mkEndpoint ["_inference", taskTypeToText tt, unInferenceId iid]
      (Just tt, Nothing) ->
        mkEndpoint ["_inference", taskTypeToText tt, "_all"]
      (Nothing, Just _) ->
        mkEndpoint ["_inference"]
      (Nothing, Nothing) ->
        mkEndpoint ["_inference"]

-- | 'runInference' builds the 'BHRequest' for
-- @POST \/_inference\/{task_type}\/{inference_id}@ (Elasticsearch 8.12+),
-- which performs inference against a previously-configured endpoint.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>.
runInference ::
  TaskType ->
  InferenceId ->
  InferenceInput ->
  BHRequest StatusDependant (ParsedEsResponse InferenceResult)
runInference taskType' inferenceId input =
  runInferenceWith taskType' inferenceId input defaultRunInferenceOptions

-- | 'runInferenceWith' is the fully-parameterised form of 'runInference'.
-- 'RunInferenceOptions' carries the @timeout@ query parameter documented at
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>:
-- an upper bound on the time the server spends on the inference call (useful,
-- as inference can be slow). Passing 'defaultRunInferenceOptions' yields a
-- request byte-for-byte identical to 'runInference'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/inference-apis.html>.
runInferenceWith ::
  TaskType ->
  InferenceId ->
  InferenceInput ->
  RunInferenceOptions ->
  BHRequest StatusDependant (ParsedEsResponse InferenceResult)
runInferenceWith taskType' inferenceId input opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode input)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          taskTypeToText taskType',
          unInferenceId inferenceId
        ]
        `withQueries` runInferenceOptionsParams opts

-- | 'streamCompletionInference' builds the 'BHRequest' for
-- @POST \/_inference\/completion\/{inference_id}\/_stream@
-- (Elasticsearch 8.12+, @operation-inference-stream-completion@), which
-- streams completion chunks (NDJSON, the opaque @_types.StreamResult@)
-- from a previously-configured completion endpoint. The full buffered
-- response body is returned as a lazy 'L.ByteString'; callers split it
-- into newline-delimited JSON chunks and decode each. The request body
-- reuses 'InferenceInput' (the @input@ \/ @task_settings@ envelope); the
-- @query@ \/ @input_type@ fields are optional and omitted when 'Nothing'.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-stream-completion>.
streamCompletionInference ::
  InferenceId ->
  InferenceInput ->
  BHRequest StatusDependant L.ByteString
streamCompletionInference inferenceId input =
  streamCompletionInferenceWith inferenceId input defaultStreamCompletionInferenceOptions

-- | 'streamCompletionInferenceWith' is the fully-parameterised form of
-- 'streamCompletionInference'. 'StreamCompletionInferenceOptions' carries
-- the @timeout@ query parameter: an upper bound on the time the server
-- spends establishing the stream. Passing
-- 'defaultStreamCompletionInferenceOptions' yields a request byte-for-byte
-- identical to 'streamCompletionInference'.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-stream-completion>.
streamCompletionInferenceWith ::
  InferenceId ->
  InferenceInput ->
  StreamCompletionInferenceOptions ->
  BHRequest StatusDependant L.ByteString
streamCompletionInferenceWith inferenceId input opts =
  postRaw endpoint (encode input)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          "completion",
          unInferenceId inferenceId,
          "_stream"
        ]
        `withQueries` streamCompletionInferenceOptionsParams opts

-- | 'streamChatCompletionInference' builds the 'BHRequest' for
-- @POST \/_inference\/chat_completion\/{inference_id}\/_stream@
-- (Elasticsearch 8.18+, @operation-inference-chat-completion-unified@),
-- which streams chat-completion chunks (NDJSON, the opaque
-- @_types.StreamResult@) from a previously-configured chat endpoint. The
-- full buffered response body is returned as a lazy 'L.ByteString'; callers
-- split it into newline-delimited JSON chunks and decode each. The request
-- body is a 'ChatCompletionRequest' (messages + optional model +
-- opaque task_settings for @tools@ \/ @reasoning@ \/ ...).
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-chat-completion-unified>.
streamChatCompletionInference ::
  InferenceId ->
  ChatCompletionRequest ->
  BHRequest StatusDependant L.ByteString
streamChatCompletionInference inferenceId request =
  streamChatCompletionInferenceWith inferenceId request defaultStreamChatCompletionInferenceOptions

-- | 'streamChatCompletionInferenceWith' is the fully-parameterised form of
-- 'streamChatCompletionInference'. 'StreamChatCompletionInferenceOptions'
-- carries the @timeout@ query parameter: an upper bound on the time the
-- server spends establishing the stream. Passing
-- 'defaultStreamChatCompletionInferenceOptions' yields a request
-- byte-for-byte identical to 'streamChatCompletionInference'.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-chat-completion-unified>.
streamChatCompletionInferenceWith ::
  InferenceId ->
  ChatCompletionRequest ->
  StreamChatCompletionInferenceOptions ->
  BHRequest StatusDependant L.ByteString
streamChatCompletionInferenceWith inferenceId request opts =
  postRaw endpoint (encode request)
  where
    endpoint =
      mkEndpoint
        [ "_inference",
          "chat_completion",
          unInferenceId inferenceId,
          "_stream"
        ]
        `withQueries` streamChatCompletionInferenceOptionsParams opts

-- $dataStreamLifecycle
--
-- /Data Stream Lifecycle/ (ES 8.x+, also in ES 9.x) manages retention
-- and downsampling for a data stream without a full ILM policy. The
-- three builders below cover the PUT (set\/update), GET (retrieve) and
-- DELETE (remove) lifecycle endpoints. The wire format is identical
-- across ES 8.x and 9.x, so the request builders live here and are
-- re-exported by the ES9 client module.

-- | 'putDataStreamLifecycle' sets or updates the lifecycle of a data
-- stream. The body carries the lifecycle configuration
-- ('DataStreamLifecycle'); the path carries the stream name. Returns
-- 'Acknowledged' on success.
--
-- Setting a lifecycle on a non-existent stream fails with an HTTP error
-- (hence 'StatusDependant'), surfaced as an 'EsError'.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/data-stream-lifecycle.html#put-data-stream-lifecycle>.
putDataStreamLifecycle ::
  DataStreamName ->
  DataStreamLifecycle ->
  BHRequest StatusDependant Acknowledged
putDataStreamLifecycle (DataStreamName dsName) lifecycle =
  put @StatusDependant ["_data_stream", dsName, "_lifecycle"] (encode lifecycle)

-- | 'putDataStreamsLifecycle' sets or updates the lifecycle of multiple
-- data streams in a single request. Issues a bulk
-- @PUT /_data_stream/_lifecycle@ with a body of the form
-- @{"data_streams":[{name,lifecycle},\u2026]}@ (see
-- 'PutDataStreamsLifecycleRequest'). Returns 'Acknowledged' on success.
--
-- Each 'DataStreamLifecycleInfo' in the request pairs a stream name
-- with its 'DataStreamLifecycle'; the @lifecycle@ field of an entry
-- may be 'Nothing' to disable lifecycle management for that stream.
-- Setting a lifecycle on a non-existent stream fails with an HTTP
-- error (hence 'StatusDependant'), surfaced as an 'EsError'.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/data-stream-lifecycle.html#put-data-streams-lifecycle>.
putDataStreamsLifecycle ::
  PutDataStreamsLifecycleRequest ->
  BHRequest StatusDependant Acknowledged
putDataStreamsLifecycle =
  put @StatusDependant ["_data_stream", "_lifecycle"] . encode

-- | 'getDataStreamLifecycle' retrieves the lifecycle configuration of
-- one or more data streams.
--
-- * @'Nothing'@ or @'Just' []@  → @GET /_data_stream/_lifecycle@
--   returns the lifecycle of every data stream on the cluster.
-- * @'Just' names@              → @GET /_data_stream\/{a,b,c}/_lifecycle@
--   returns only the lifecycles of the named data streams (comma-joined
--   path segment, mirroring 'getDataStreams').
--
-- Streams that are not managed by a lifecycle appear in the response
-- with their @lifecycle@ field decoded as 'Nothing'.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/data-stream-lifecycle.html#get-data-stream-lifecycle>.
getDataStreamLifecycle ::
  Maybe [DataStreamName] ->
  BHRequest StatusDependant GetDataStreamLifecyclesResponse
getDataStreamLifecycle mDataStreamNames =
  get @StatusDependant endpoint
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream", "_lifecycle"]
      Just [] -> ["_data_stream", "_lifecycle"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest)), "_lifecycle"]
    renderDataStreamName (DataStreamName dsName) = dsName

-- | 'deleteDataStreamLifecycle' removes the lifecycle configuration
-- from a data stream, rendering it unmanaged by the data stream
-- lifecycle. Returns 'Acknowledged' on success.
--
-- Deleting the lifecycle of a non-existent stream fails with an HTTP
-- error (hence 'StatusDependant'), surfaced as an 'EsError'.
--
-- See
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/data-stream-lifecycle.html#delete-data-stream-lifecycle>.
deleteDataStreamLifecycle ::
  DataStreamName ->
  BHRequest StatusDependant Acknowledged
deleteDataStreamLifecycle (DataStreamName dsName) =
  delete @StatusDependant ["_data_stream", dsName, "_lifecycle"]

-- $dataStreamOptions
--
-- /Data Stream Options/ (ES 8.19+, also in ES 9.x) expose per-stream
-- feature toggles that live outside the lifecycle and ILM machinery
-- (currently just the @failure_store@). The wire format is identical
-- across ES 8.19+ and 9.x, so the request builders live here and are
-- re-exported by the ES9 client module.
--
-- /Path-segment asymmetry/: 'getDataStreamOptions' omits the name
-- segment when given @'Nothing'@ or @'Just' []@ (matching
-- 'getDataStreams'); 'updateDataStreamOptions' and
-- 'deleteDataStreamOptions' substitute the wildcard @*@ in that case,
-- because the ES API requires the @{name}@ segment on PUT and DELETE.
-- The wildcard is a cluster-wide fan-out — see the @CAUTION@ haddock
-- on each builder.

-- | 'getDataStreamOptions' retrieves the options (e.g. failure store
-- configuration) of one or more data streams.
--
-- * @'Nothing'@ or @'Just' []@  → @GET /_data_stream/_options@
--   returns the options of every data stream on the cluster.
-- * @'Just' names@              → @GET /_data_stream\/{a,b,c}/_options@
--   returns only the options of the named data streams (comma-joined
--   path segment, mirroring 'getDataStreams'). Wildcards (@*@) and
--   @_all@ are accepted by the server in place of literal names.
--
-- Streams that have no options set appear in the response with their
-- @options@ field decoded as 'Nothing'.
--
-- See
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-get-data-stream-options>.
getDataStreamOptions ::
  Maybe [DataStreamName] ->
  BHRequest StatusDependant GetDataStreamOptionsResponse
getDataStreamOptions mDataStreamNames =
  get @StatusDependant endpoint
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream", "_options"]
      Just [] -> ["_data_stream", "_options"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest)), "_options"]
    renderDataStreamName (DataStreamName dsName) = dsName

-- | 'updateDataStreamOptions' sets the options (e.g. failure store
-- configuration) of one or more data streams. The body carries an
-- 'UpdateDataStreamOptionsRequest'; the @failure_store@ override it
-- describes is applied to every stream resolved by the path's
-- name expression (literal names, comma-separated lists or
-- wildcards — see 'getDataStreamOptions').
--
-- /CAUTION/: when given @'Nothing'@ or @'Just' []@, the builder
-- substitutes the wildcard @*@ for the @{name}@ path segment, so the
-- @failure_store@ override is applied to /every/ data stream on the
-- cluster. Pass an explicit 'DataStreamName' list if you want to
-- scope the update.
--
-- Referencing a non-existent stream surfaces as an 'EsError'. The
-- server's reaction to an empty PUT body (@{ }@, no @failure_store@
-- key) is not specified by the ES docs and may be rejected; prefer
-- sending an explicit 'DataStreamFailureStore'.
--
-- See
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-put-data-stream-options>.
updateDataStreamOptions ::
  Maybe [DataStreamName] ->
  UpdateDataStreamOptionsRequest ->
  BHRequest StatusDependant Acknowledged
updateDataStreamOptions mDataStreamNames req =
  put @StatusDependant endpoint (encode req)
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream", "*", "_options"]
      Just [] -> ["_data_stream", "*", "_options"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest)), "_options"]
    renderDataStreamName (DataStreamName dsName) = dsName

-- | 'deleteDataStreamOptions' removes the options (e.g. failure store
-- configuration) from one or more data streams, resetting them to
-- their defaults. Returns 'Acknowledged' on success. The path's name
-- expression follows the same comma-separated\/wildcard rules as
-- 'getDataStreamOptions'.
--
-- /CAUTION/: when given @'Nothing'@ or @'Just' []@, the builder
-- substitutes the wildcard @*@ for the @{name}@ path segment, so the
-- options are reset on /every/ data stream on the cluster. Pass an
-- explicit 'DataStreamName' list if you want to scope the deletion.
--
-- Deleting the options of a non-existent stream fails with an HTTP
-- error (hence 'StatusDependant'), surfaced as an 'EsError'.
--
-- See
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-delete-data-stream-options>.
deleteDataStreamOptions ::
  Maybe [DataStreamName] ->
  BHRequest StatusDependant Acknowledged
deleteDataStreamOptions mDataStreamNames =
  delete @StatusDependant endpoint
  where
    endpoint = case mDataStreamNames of
      Nothing -> ["_data_stream", "*", "_options"]
      Just [] -> ["_data_stream", "*", "_options"]
      Just (first : rest) ->
        ["_data_stream", T.intercalate "," (renderDataStreamName <$> (first : rest)), "_options"]
    renderDataStreamName (DataStreamName dsName) = dsName

-- $dataStreamLifecycleStatsAndExplain
--
-- /Data Stream Lifecycle Stats and Explain/ (ES 8.11+ for explain,
-- 8.12+ for stats, also in ES 9.x) report runtime status of the data
-- stream lifecycle service. Both endpoints live at the index level
-- (no @_data_stream@ path segment) because the lifecycle service
-- operates on backing indices rather than on streams directly. The
-- wire format is identical across ES 8.x and 9.x, so the request
-- builders live here and are re-exported by the ES9 client module.

-- | 'getDataStreamLifecycleStats' retrieves cluster-wide runtime
-- statistics for the data stream lifecycle service. Issues
-- @GET /_lifecycle/stats@. The response carries aggregate timing
-- information plus a per-stream breakdown of backing-index health
-- (see 'DataStreamLifecycleStats').
--
-- The endpoint takes no path or query parameters; the response is
-- always a JSON body with the stats payload.
--
-- See
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-get-data-lifecycle-stats>.
getDataStreamLifecycleStats ::
  BHRequest StatusDependant DataStreamLifecycleStats
getDataStreamLifecycleStats =
  get @StatusDependant ["_lifecycle", "stats"]

-- | URI query parameters accepted by
-- @GET /{index}/_lifecycle/explain@. The 'ExplainDataStreamLifecycleOptions'
-- record is defined in "Database.Bloodhound.Internal.Versions.ElasticSearch7.Types.DataStream"
-- (alongside the rest of the lifecycle types); this helper renders it
-- as a list of @(key, value)@ query parameters suitable for 'withQueries'.
-- 'Nothing' fields are omitted; booleans render as @"true"@\/@"false"@.
explainDataStreamLifecycleOptionsParams :: ExplainDataStreamLifecycleOptions -> [(Text, Maybe Text)]
explainDataStreamLifecycleOptionsParams opts =
  catMaybes
    [ ("include_defaults",) . Just . boolQP <$> explainDataStreamLifecycleOptionsIncludeDefaults opts,
      ("master_timeout",) . Just <$> explainDataStreamLifecycleOptionsMasterTimeout opts
    ]
  where
    boolQP True = "true"
    boolQP False = "false"

-- | 'explainDataStreamLifecycle' retrieves the per-index lifecycle
-- status for one or more backing indices. Equivalent to
-- @'explainDataStreamLifecycleWith' indices 'defaultExplainDataStreamLifecycleOptions'@.
--
-- * @'Nothing'@ or @'Just' []@  → @GET /_lifecycle/explain@ (the
--   server interprets the missing @{index}@ segment as \"all
--   lifecycle-managed indices\").
-- * @'Just' names@              → @GET /{a,b,c}/_lifecycle/explain@
--   explains only the named backing indices (comma-joined path
--   segment). Backing index names typically look like
--   @.ds-<stream>-<date>-<generation>@.
--
-- See 'ExplainDataStreamLifecycleResponse' for the @indices@-map
-- response shape, and the operation docs at
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-explain-data-lifecycle>.
explainDataStreamLifecycle ::
  Maybe [IndexName] ->
  BHRequest StatusDependant ExplainDataStreamLifecycleResponse
explainDataStreamLifecycle indices =
  explainDataStreamLifecycleWith indices defaultExplainDataStreamLifecycleOptions

-- | 'explainDataStreamLifecycleWith' is the fully-parameterised form
-- of 'explainDataStreamLifecycle'. Pass
-- 'defaultExplainDataStreamLifecycleOptions' to reproduce a
-- parameterless call.
explainDataStreamLifecycleWith ::
  Maybe [IndexName] ->
  ExplainDataStreamLifecycleOptions ->
  BHRequest StatusDependant ExplainDataStreamLifecycleResponse
explainDataStreamLifecycleWith mIndices opts =
  get @StatusDependant (endpoint `withQueries` explainDataStreamLifecycleOptionsParams opts)
  where
    endpoint = case mIndices of
      Nothing -> ["_lifecycle", "explain"]
      Just [] -> ["_lifecycle", "explain"]
      Just (first : rest) ->
        [T.intercalate "," (unIndexName <$> (first : rest)), "_lifecycle", "explain"]

-- | 'eqlSearch' builds the 'BHRequest' for the EQL
-- @POST /{index}/_eql/search@ endpoint (Elasticsearch 8.x). The endpoint
-- path is identical to ES 7.10+, but the 8.x spec adds the
-- @allow_partial_search_results@, @allow_partial_sequence_results@ and
-- @case_sensitive@ body fields (plus the matching URI parameters), so the
-- ES8 builder uses the 'EQLRequestBodyES8' newtype and
-- 'eqlSearchOptionsParams8' rather than the ES7 'Requests7.eqlSearch'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/eql-search-api.html>.
eqlSearch ::
  (FromJSON a) =>
  IndexName ->
  EQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
eqlSearch indexName = eqlSearchWith indexName defaultEQLSearchOptions

-- | 'eqlSearchWith' is the fully-parameterised form of 'eqlSearch'
-- (Elasticsearch 8.x). The endpoint path matches ES 7.10+, but the body
-- is encoded via the 'EQLRequestBodyES8' newtype (which emits the 8.x
-- body fields @allow_partial_search_results@,
-- @allow_partial_sequence_results@ and @case_sensitive@) and the URI
-- parameters are rendered with 'eqlSearchOptionsParams8' (which emits
-- the 8.x-only @allow_partial_search_results@ and
-- @allow_partial_sequence_results@). See 'Requests7.eqlSearchWith' for
-- the ES7 variant that gates these out.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/eql-search-api.html>.
eqlSearchWith ::
  (FromJSON a) =>
  IndexName ->
  EQLSearchOptions ->
  EQLRequest ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
eqlSearchWith indexName opts req =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint (encode (EQLRequestBodyES8 req))
  where
    endpoint =
      mkEndpoint [unIndexName indexName, "_eql", "search"]
        `withQueries` eqlSearchOptionsParams8 opts

-- | 'getEQLSearch' builds the 'BHRequest' for the async EQL
-- @GET /_eql/search\/{id}@ endpoint (Elasticsearch 8.x). The wire format
-- is identical to ES 7.10+, so the ES7 request builder is reused
-- verbatim.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-get>.
getEQLSearch ::
  (FromJSON a) =>
  EQLSearchId ->
  Maybe Text ->
  BHRequest StatusDependant (ParsedEsResponse (EQLResult a))
getEQLSearch = Requests7.getEQLSearch

-- | 'getEQLStatus' builds the 'BHRequest' for the async EQL status
-- @GET /_eql/search/status\/{id}@ endpoint (Elasticsearch 8.x). The wire
-- format is identical to ES 7.10+, so the ES7 request builder is reused
-- verbatim.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-get-status>.
getEQLStatus ::
  EQLSearchId ->
  BHRequest StatusDependant EQLStatus
getEQLStatus = Requests7.getEQLStatus

-- | 'deleteEQLSearch' builds the 'BHRequest' for the EQL
-- @DELETE /_eql/search/{id}@ endpoint on Elasticsearch 8. The wire
-- format is identical to ES 7.10+, so the ES7 request builder is
-- reused verbatim.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-delete>.
deleteEQLSearch ::
  EQLSearchId ->
  BHRequest StatusIndependant Acknowledged
deleteEQLSearch = Requests7.deleteEQLSearch

-- | 'getTermsEnum' builds the 'BHRequest' for the terms enum
-- @GET \/{index}\/_terms_enum@ endpoint (Elasticsearch >= 7.10). The
-- wire format is identical to ES 7.10+, so the ES7 request builder is
-- reused verbatim.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-terms-enum.html>.
getTermsEnum ::
  Maybe [IndexName] ->
  FieldName ->
  Maybe Text ->
  BHRequest StatusDependant TermsEnumResponse
getTermsEnum = Requests7.getTermsEnum

-- | 'getTermsEnumWith' is the fully-parameterised variant of
-- 'getTermsEnum' (ES >= 7.10). The wire format is identical to
-- ES 7.10+, so the ES7 request builder is reused verbatim.
getTermsEnumWith ::
  Maybe [IndexName] ->
  TermsEnumOptions ->
  TermsEnumRequest ->
  BHRequest StatusDependant TermsEnumResponse
getTermsEnumWith = Requests7.getTermsEnumWith

-- | 'putQueryRuleset' builds the 'BHRequest' for
-- @PUT \/_query_rules\/{ruleset_id}@ (Elasticsearch 8.9+), which
-- creates or replaces a query ruleset. The body is the encoded
-- 'QueryRuleset'; the server returns a 'QueryRulesetPutResponse'
-- whose @result@ field is @"created"@ or @"updated"@.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/put-query-ruleset.html>.
putQueryRuleset ::
  RulesetId ->
  QueryRuleset ->
  BHRequest StatusDependant QueryRulesetPutResponse
putQueryRuleset rulesetId ruleset =
  put @StatusDependant endpoint (encode ruleset)
  where
    endpoint = mkEndpoint ["_query_rules", unRulesetId rulesetId]

-- | 'getQueryRuleset' builds the 'BHRequest' for
-- @GET \/_query_rules\/{ruleset_id}@ (Elasticsearch 8.9+), which
-- returns the stored ruleset and its metadata. A @404@ for a missing
-- ruleset surfaces as an 'EsError'; wrap with 'tryPerformBHRequest'
-- for miss-tolerant reads.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/get-query-ruleset.html>.
getQueryRuleset ::
  RulesetId ->
  BHRequest StatusDependant QueryRulesetInfo
getQueryRuleset rulesetId =
  get @StatusDependant endpoint
  where
    endpoint = mkEndpoint ["_query_rules", unRulesetId rulesetId]

-- | 'deleteQueryRuleset' builds the 'BHRequest' for
-- @DELETE \/_query_rules\/{ruleset_id}@ (Elasticsearch 8.9+), which
-- removes a ruleset. Returns 'Acknowledged' on success. Deleting a
-- non-existent ruleset surfaces as an 'EsError'; wrap with
-- 'tryPerformBHRequest' for miss-tolerant deletion.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/delete-query-ruleset.html>.
deleteQueryRuleset ::
  RulesetId ->
  BHRequest StatusDependant Acknowledged
deleteQueryRuleset rulesetId =
  delete (mkEndpoint ["_query_rules", unRulesetId rulesetId])

-- | 'testQueryRuleset' builds the 'BHRequest' for
-- @POST \/_query_rules\/{ruleset_id}\/_test@ (Elasticsearch 8.10+),
-- which evaluates the supplied 'QueryRulesetTest' match criteria
-- against every rule in the stored ruleset and reports the matching
-- rule identifiers. The body is the encoded 'QueryRulesetTest'; the
-- server returns a 'QueryRulesetTestResponse'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/test-query-ruleset.html>.
testQueryRuleset ::
  RulesetId ->
  QueryRulesetTest ->
  BHRequest StatusDependant QueryRulesetTestResponse
testQueryRuleset rulesetId body =
  post @StatusDependant endpoint (encode body)
  where
    endpoint = mkEndpoint ["_query_rules", unRulesetId rulesetId, "_test"]

-- | 'putSearchApplication' builds the 'BHRequest' for
-- @PUT \/_application\/search_application\/{name}@ (Elasticsearch 8.6+),
-- which creates or replaces a search application. The body is a
-- 'SearchApplication' record: its 'saIndices' field (a non-empty list of
-- 'IndexName', encoded as the @indices@ JSON array) is required and
-- names the search application's target indices, while
-- 'saAnalyticsCollectionName' (encoded as @analytics_collection_name@)
-- and 'saTemplate' (encoded as @template@) are optional. The server
-- returns a 'SearchApplicationPutResponse' whose @result@ field is
-- @"created"@ or @"updated"@.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-apis.html>.
putSearchApplication ::
  SearchApplicationName ->
  SearchApplication ->
  BHRequest StatusDependant SearchApplicationPutResponse
putSearchApplication appName app =
  putSearchApplicationWith appName defaultSearchApplicationPutOptions app

-- | 'putSearchApplicationWith' is the fully-parameterised form of
-- 'putSearchApplication'. 'SearchApplicationPutOptions' carries the
-- @create@ query parameter documented at
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v8/operation/operation-search-application-put>:
-- passing @'Just' 'True'@ emits @?create=true@, which makes the request
-- fail if the search application already exists. Passing
-- 'defaultSearchApplicationPutOptions' yields a request byte-for-byte
-- identical to 'putSearchApplication'.
putSearchApplicationWith ::
  SearchApplicationName ->
  SearchApplicationPutOptions ->
  SearchApplication ->
  BHRequest StatusDependant SearchApplicationPutResponse
putSearchApplicationWith appName opts app =
  put @StatusDependant endpoint (encode app)
  where
    endpoint =
      mkEndpoint ["_application", "search_application", unSearchApplicationName appName]
        `withQueries` queries
    queries =
      catMaybes
        [ ("create",) . Just . boolText <$> sapoCreate opts
        ]
    boolText :: Bool -> Text
    boolText True = "true"
    boolText False = "false"

-- | 'getSearchApplication' builds the 'BHRequest' for
-- @GET \/_application\/search_application\/{name}@ (Elasticsearch 8.6+),
-- which returns the stored search application and its metadata. A @404@
-- for a missing application surfaces as an 'EsError'; wrap with
-- 'tryPerformBHRequest' for miss-tolerant reads.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-apis.html>.
getSearchApplication ::
  SearchApplicationName ->
  BHRequest StatusDependant SearchApplicationInfo
getSearchApplication appName =
  get @StatusDependant endpoint
  where
    endpoint = mkEndpoint ["_application", "search_application", unSearchApplicationName appName]

-- | 'deleteSearchApplication' builds the 'BHRequest' for
-- @DELETE \/_application\/search_application\/{name}@ (Elasticsearch
-- 8.6+), which removes a search application. Returns 'Acknowledged' on
-- success. Deleting a non-existent application surfaces as an
-- 'EsError'; wrap with 'tryPerformBHRequest' for miss-tolerant
-- deletion.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-apis.html>.
deleteSearchApplication ::
  SearchApplicationName ->
  BHRequest StatusDependant Acknowledged
deleteSearchApplication appName =
  delete (mkEndpoint ["_application", "search_application", unSearchApplicationName appName])

-- | 'searchApplication' builds the 'BHRequest' for
-- @POST \/_application\/search_application\/{name}\/_search@
-- (Elasticsearch 8.6+), which runs the search application's stored
-- template against its indices. The optional
-- 'SearchApplicationSearchParams' carries per-request template
-- parameter overrides; 'Nothing' runs the template with its defaults.
-- The response is a regular 'SearchResult' parameterized by the
-- document-source type @a@.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-apis.html>.
searchApplication ::
  (FromJSON a) =>
  SearchApplicationName ->
  Maybe SearchApplicationSearchParams ->
  BHRequest StatusDependant (ParsedEsResponse (SearchResult a))
searchApplication appName mParams =
  searchApplicationWith appName mParams defaultSearchApplicationOptions

-- | 'searchApplicationWith' is the fully-parameterised form of
-- 'searchApplication'. 'SearchApplicationOptions' carries the @typed_keys@
-- query parameter documented at
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v8/operation/operation-search-application-search>:
-- when @'Just' 'True'@ the response returns aggregation names prefixed with
-- their type (e.g. @aggregations#terms@). Passing
-- 'defaultSearchApplicationOptions' yields a request byte-for-byte identical
-- to 'searchApplication'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-apis.html>.
searchApplicationWith ::
  (FromJSON a) =>
  SearchApplicationName ->
  Maybe SearchApplicationSearchParams ->
  SearchApplicationOptions ->
  BHRequest StatusDependant (ParsedEsResponse (SearchResult a))
searchApplicationWith appName mParams opts =
  withBHResponseParsedEsResponse $
    post @StatusDependant endpoint body
  where
    endpoint =
      mkEndpoint ["_application", "search_application", unSearchApplicationName appName, "_search"]
        `withQueries` searchApplicationOptionsParams opts
    body = maybe L.empty encode mParams

-- | 'listSearchApplications' builds the 'BHRequest' for
-- @GET \/_application\/search_application@ (Elasticsearch 8.6+), which
-- lists configured search applications. Returns a
-- 'SearchApplicationListResponse' whose @results@ each reuse the
-- 'SearchApplicationInfo' shape (typically carrying only @name@ and
-- @updated_at_millis@ per item). Passing
-- 'listSearchApplicationsWith' with 'defaultSearchApplicationListOptions'
-- is byte-for-byte identical to this function; use it to attach the
-- @q@\/@from@\/@size@ query parameters.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/list-search-applications.html>.
listSearchApplications ::
  BHRequest StatusDependant SearchApplicationListResponse
listSearchApplications =
  listSearchApplicationsWith defaultSearchApplicationListOptions

-- | 'listSearchApplicationsWith' is the fully-parameterised form of
-- 'listSearchApplications'. 'SearchApplicationListOptions' carries the
-- @q@ (Lucene query-string filter), @from@ (offset) and @size@ (max
-- results) query parameters documented at
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v8/operation/operation-search-application-list>.
-- Passing 'defaultSearchApplicationListOptions' yields a request
-- byte-for-byte identical to 'listSearchApplications'.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/list-search-applications.html>.
listSearchApplicationsWith ::
  SearchApplicationListOptions ->
  BHRequest StatusDependant SearchApplicationListResponse
listSearchApplicationsWith opts =
  get @StatusDependant endpoint
  where
    endpoint =
      mkEndpoint ["_application", "search_application"]
        `withQueries` searchApplicationListOptionsParams opts

-- | 'renderSearchApplicationQuery' builds the 'BHRequest' for
-- @POST \/_application\/search_application\/{name}\/_render_query@
-- (Elasticsearch 8.6+), which renders the search application's stored
-- template against the supplied parameters and returns the resulting
-- Elasticsearch query body /without/ executing the search. The optional
-- 'SearchApplicationSearchParams' carries per-request template parameter
-- overrides — the same body shape as 'searchApplication' — and
-- 'Nothing' renders the template with its defaults. The response is the
-- rendered search-request body (e.g. @from@, @size@, @query@, @explain@)
-- returned as an opaque 'Value', mirroring how
-- 'SearchApplicationScriptBody' keeps inline search bodies untyped.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/search-application-render-query.html>.
renderSearchApplicationQuery ::
  SearchApplicationName ->
  Maybe SearchApplicationSearchParams ->
  BHRequest StatusDependant Value
renderSearchApplicationQuery appName mParams =
  post @StatusDependant endpoint body
  where
    endpoint =
      mkEndpoint ["_application", "search_application", unSearchApplicationName appName, "_render_query"]
    body = maybe L.empty encode mParams

-- | 'putSynonymsSet' builds the 'BHRequest' for
-- @PUT \/_synonyms\/{synonyms_set_id}@ (Elasticsearch 8.10+), which
-- creates or replaces a synonyms set. The body is the encoded
-- 'SynonymsSet'; the server returns a 'SynonymsSetPutResponse' whose
-- @acknowledged@ and/or @result@ fields are populated depending on
-- whether analyzer reloading occurred.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/put-synonyms-set.html>.
putSynonymsSet ::
  SynonymsSetId ->
  SynonymsSet ->
  BHRequest StatusDependant SynonymsSetPutResponse
putSynonymsSet setId body =
  put @StatusDependant endpoint (encode body)
  where
    endpoint = mkEndpoint ["_synonyms", unSynonymsSetId setId]

-- | 'getSynonymsSet' builds the 'BHRequest' for
-- @GET \/_synonyms\/{synonyms_set_id}@ (Elasticsearch 8.10+), which
-- returns the stored synonyms set. Equivalent to
-- @'getSynonymsSetWith' setId 'defaultSynonymsGetOptions'@ — emits no
-- query string and relies on the server-side @from@\/@size@ defaults
-- (@0@ and @10@). A @404@ for a missing set surfaces as an 'EsError';
-- wrap with 'tryPerformBHRequest' for miss-tolerant reads.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/get-synonyms-set.html>.
getSynonymsSet ::
  SynonymsSetId ->
  BHRequest StatusDependant SynonymsSetInfo
getSynonymsSet setId =
  getSynonymsSetWith setId defaultSynonymsGetOptions

-- | 'getSynonymsSetWith' is the fully-parameterised variant of
-- 'getSynonymsSet'. 'SynonymsGetOptions' carries the @from@ (offset)
-- and @size@ (max synonym rules) query parameters documented at
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/get-synonyms-set.html>.
-- Passing 'defaultSynonymsGetOptions' yields a request byte-for-byte
-- identical to 'getSynonymsSet'.
getSynonymsSetWith ::
  SynonymsSetId ->
  SynonymsGetOptions ->
  BHRequest StatusDependant SynonymsSetInfo
getSynonymsSetWith setId opts =
  get @StatusDependant endpoint
  where
    endpoint =
      mkEndpoint ["_synonyms", unSynonymsSetId setId]
        `withQueries` synonymsGetOptionsParams opts

-- | 'deleteSynonymsSet' builds the 'BHRequest' for
-- @DELETE \/_synonyms\/{synonyms_set_id}@ (Elasticsearch 8.10+), which
-- removes a synonyms set. Returns 'Acknowledged' on success. Deleting
-- a non-existent set surfaces as an 'EsError'; wrap with
-- 'tryPerformBHRequest' for miss-tolerant deletion.
--
-- For more information see
-- <https://www.elastic.co/guide/en/elasticsearch/reference/8.17/delete-synonyms-set.html>.
deleteSynonymsSet ::
  SynonymsSetId ->
  BHRequest StatusDependant Acknowledged
deleteSynonymsSet setId =
  delete (mkEndpoint ["_synonyms", unSynonymsSetId setId])

------------------------------------------------------------------------------
-- Behavioral analytics (/_application/analytics/*)
------------------------------------------------------------------------------

-- $behavioralAnalytics
--
-- /Behavioral analytics/ (Elasticsearch 8.7+) backs the search-curator
-- feature: a named /collection/ is a sink for client-side search events
-- (searches, clicks, page views), backed by an Elasticsearch index.
--
-- * @GET /_application/analytics@ — list every collection
--   ('getAnalyticsCollections' with 'Nothing').
-- * @GET /_application/analytics/{name}@ — fetch a single collection
--   ('getAnalyticsCollections' with @'Just' name@).
-- * @PUT /_application/analytics/{name}@ — create or replace a
--   collection ('putAnalyticsCollection').
-- * @DELETE /_application/analytics/{name}@ — delete a collection and
--   its backing index ('deleteAnalyticsCollection').
-- * @POST /_application/analytics/{collection_name}/event/{event_type}@ —
--   ingest a single behavioral event ('postAnalyticsEvent' /
--   'postAnalyticsEventWith').

-- | 'getAnalyticsCollections' lists behavioral analytics collections.
-- Maps to the @GET /_application/analytics@ family:
--
-- * @'Nothing'@  → @GET /_application/analytics@ returns every
--   collection.
-- * @'Just' name@ → @GET /_application/analytics/{name}@ returns only
--   the named collection.
--
-- The server always answers with a JSON object keyed by collection
-- name; the 'AnalyticsCollectionsResponse' decoder flattens that map
-- into a list of 'AnalyticsCollection' (each carrying its name).
getAnalyticsCollections ::
  Maybe AnalyticsCollectionName ->
  BHRequest StatusDependant [AnalyticsCollection]
getAnalyticsCollections mName =
  unAnalyticsCollectionsResponse <$> get @StatusDependant endpoint
  where
    endpoint = case mName of
      Nothing -> mkEndpoint ["_application", "analytics"]
      Just (AnalyticsCollectionName n) ->
        mkEndpoint ["_application", "analytics", n]

-- | 'putAnalyticsCollection' creates or replaces a behavioral analytics
-- collection. Maps to @PUT /_application/analytics/{name}@. The endpoint
-- takes no request body in its documented shape; the server mints the
-- backing index from the collection name.
putAnalyticsCollection ::
  AnalyticsCollectionName ->
  BHRequest StatusDependant AnalyticsCollectionPutResponse
putAnalyticsCollection (AnalyticsCollectionName n) =
  put @StatusDependant ["_application", "analytics", n] emptyBody

-- | 'deleteAnalyticsCollection' deletes a behavioral analytics
-- collection and its backing index. Maps to
-- @DELETE /_application/analytics/{name}@. Deleting a non-existent
-- collection surfaces as an 'EsError'; wrap with 'tryPerformBHRequest'
-- for miss-tolerant deletion.
deleteAnalyticsCollection ::
  AnalyticsCollectionName ->
  BHRequest StatusDependant Acknowledged
deleteAnalyticsCollection (AnalyticsCollectionName n) =
  delete (mkEndpoint ["_application", "analytics", n])

-- | 'postAnalyticsEvent' ingests a single behavioral event. Maps to
-- @POST /_application/analytics/{collection_name}/event/{event_type}@.
-- The event payload is an opaque 'Value' (the schema is event-type
-- specific and caller-defined). Equivalent to
-- @'postAnalyticsEventWith' 'defaultAnalyticsEventOptions'@.
postAnalyticsEvent ::
  AnalyticsCollectionName ->
  AnalyticsEventType ->
  Value ->
  BHRequest StatusDependant AnalyticsEventResponse
postAnalyticsEvent =
  postAnalyticsEventWith defaultAnalyticsEventOptions

-- | 'postAnalyticsEventWith' is the fully-parameterised form of
-- 'postAnalyticsEvent'. 'AnalyticsEventOptions' carries the @debug@
-- query parameter: @'Just' 'True'@ validates the event without
-- persisting it.
postAnalyticsEventWith ::
  AnalyticsEventOptions ->
  AnalyticsCollectionName ->
  AnalyticsEventType ->
  Value ->
  BHRequest StatusDependant AnalyticsEventResponse
postAnalyticsEventWith opts (AnalyticsCollectionName coll) (AnalyticsEventType evType) payload =
  post @StatusDependant endpoint (encode payload)
  where
    endpoint =
      mkEndpoint ["_application", "analytics", coll, "event", evType]
        `withQueries` analyticsEventOptionsParams opts

------------------------------------------------------------------------------
-- Connectors (/_connector/*)
------------------------------------------------------------------------------

-- $connectors
--
-- /Connectors/ (Elasticsearch 8.8+) are the configuration entities the
-- Elastic Connector Framework uses to sync an external data source into
-- an Elasticsearch index. The API splits into connector CRUD +
-- per-field @update-*@ endpoints (under @/_connector/{id}@) and a
-- parallel sync-job surface (under @/_connector/_sync_job/{id}@). ES8
-- only — no ES7 nor OpenSearch equivalent on this path.
--
-- The response envelopes are intentionally not uniform (see
-- "Database.Bloodhound.Internal.Versions.ElasticSearch8.Types.Connectors"):
-- GET returns the bare entity; create returns @{"result","id"}@;
-- mutations return @{"result"}@ or an opaque object; DELETE returns
-- @{"acknowledged"}@; list returns @{"count","results"}@.

-- Connector path helpers. Each returns a ready-to-use 'Endpoint' (the
-- verb functions expect an 'Endpoint', and 'OverloadedLists' only
-- rewrites list /literals/, not list-typed values, so the helpers apply
-- 'mkEndpoint' themselves).
connectorPath :: ConnectorId -> [Text] -> Endpoint
connectorPath (ConnectorId cid) suffix =
  mkEndpoint (["_connector", cid] <> suffix)

syncJobPath :: ConnectorSyncJobId -> [Text] -> Endpoint
syncJobPath (ConnectorSyncJobId jid) suffix =
  mkEndpoint (["_connector", "_sync_job", jid] <> suffix)

-- | 'createConnector' creates a connector. Maps to @POST /_connector@.
-- The server mints the id and returns 'ConnectorCreateResponse'.
createConnector ::
  CreateConnectorRequest ->
  BHRequest StatusDependant ConnectorCreateResponse
createConnector body =
  post @StatusDependant ["_connector"] (encode body)

-- | 'putConnector' creates or fully replaces a connector by id. Maps to
-- @PUT /_connector/{connector_id}@.
putConnector ::
  ConnectorId ->
  CreateConnectorRequest ->
  BHRequest StatusDependant ConnectorCreateResponse
putConnector cid body =
  put @StatusDependant (connectorPath cid []) (encode body)

-- | 'getConnector' fetches a connector by id. Maps to
-- @GET /_connector/{connector_id}@.
getConnector ::
  ConnectorId ->
  BHRequest StatusDependant Connector
getConnector cid = get @StatusDependant (connectorPath cid [])

-- | 'deleteConnector' deletes a connector by id. Maps to
-- @DELETE /_connector/{connector_id}@. Equivalent to
-- @'deleteConnectorWith' 'Nothing'@.
deleteConnector ::
  ConnectorId ->
  BHRequest StatusDependant Acknowledged
deleteConnector = deleteConnectorWith Nothing

-- | 'deleteConnectorWith' is the fully-parameterised form of
-- 'deleteConnector'. Setting @'Just' 'True'@ also deletes the
-- connector's pending sync jobs (@?delete_sync_jobs=true@).
deleteConnectorWith ::
  Maybe Bool ->
  ConnectorId ->
  BHRequest StatusDependant Acknowledged
deleteConnectorWith mDeleteSyncJobs cid =
  delete (connectorPath cid [] `withQueries` qs)
  where
    qs = catMaybes [("delete_sync_jobs",) . Just . boolQP <$> mDeleteSyncJobs]
    boolQP True = "true"
    boolQP False = "false"

-- | 'listConnectors' lists configured connectors. Maps to
-- @GET /_connector@. Equivalent to @'listConnectorsWith'
-- 'defaultConnectorListOptions'@.
listConnectors ::
  BHRequest StatusDependant ConnectorListResponse
listConnectors = listConnectorsWith defaultConnectorListOptions

-- | 'listConnectorsWith' is the fully-parameterised form of
-- 'listConnectors'.
listConnectorsWith ::
  ConnectorListOptions ->
  BHRequest StatusDependant ConnectorListResponse
listConnectorsWith opts =
  get @StatusDependant
    (mkEndpoint ["_connector"] `withQueries` connectorListOptionsParams opts)

-- | 'checkInConnector' records a connector heartbeat. Maps to
-- @PUT /_connector/{connector_id}/_check_in@ and returns
-- 'ConnectorMutationResult'.
checkInConnector ::
  ConnectorId ->
  BHRequest StatusDependant ConnectorMutationResult
checkInConnector cid =
  put @StatusDependant (connectorPath cid ["_check_in"]) emptyBody

-- | 'updateConnectorApiKeyId' sets the connector's API key id + secret
-- id. Maps to @PUT /_connector/{connector_id}/_api_key_id@.
updateConnectorApiKeyId ::
  ConnectorId ->
  UpdateConnectorApiKeyRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorApiKeyId cid body =
  put @StatusDependant (connectorPath cid ["_api_key_id"]) (encode body)

-- | 'updateConnectorConfiguration' updates the connector configuration
-- field. Maps to @PUT /_connector/{connector_id}/_configuration@.
updateConnectorConfiguration ::
  ConnectorId ->
  UpdateConnectorConfigurationRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorConfiguration cid body =
  put @StatusDependant (connectorPath cid ["_configuration"]) (encode body)

-- | 'updateConnectorError' sets or clears the connector error field.
-- Maps to @PUT /_connector/{connector_id}/_error@.
updateConnectorError ::
  ConnectorId ->
  UpdateConnectorErrorRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorError cid body =
  put @StatusDependant (connectorPath cid ["_error"]) (encode body)

-- | 'updateConnectorFeatures' overrides DLS / incremental / sync-rule
-- features. Maps to @PUT /_connector/{connector_id}/_features@.
updateConnectorFeatures ::
  ConnectorId ->
  UpdateConnectorFeaturesRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorFeatures cid body =
  put @StatusDependant (connectorPath cid ["_features"]) (encode body)

-- | 'updateConnectorFiltering' updates the connector draft filtering.
-- Maps to @PUT /_connector/{connector_id}/_filtering@.
updateConnectorFiltering ::
  ConnectorId ->
  UpdateConnectorFilteringRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorFiltering cid body =
  put @StatusDependant (connectorPath cid ["_filtering"]) (encode body)

-- | 'updateConnectorDraftFilteringValidation' updates the draft
-- filtering validation info. Maps to
-- @PUT /_connector/{connector_id}/_filtering/_validation@.
updateConnectorDraftFilteringValidation ::
  ConnectorId ->
  UpdateConnectorFilteringValidationRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorDraftFilteringValidation cid body =
  put
    @StatusDependant
    (connectorPath cid ["_filtering", "_validation"])
    (encode body)

-- | 'activateConnectorDraftFiltering' activates the valid draft filter.
-- Maps to @PUT /_connector/{connector_id}/_filtering/_activate@.
activateConnectorDraftFiltering ::
  ConnectorId ->
  BHRequest StatusDependant ConnectorMutationResult
activateConnectorDraftFiltering cid =
  put
    @StatusDependant
    (connectorPath cid ["_filtering", "_activate"])
    emptyBody

-- | 'updateConnectorIndexName' sets (or, with 'Nothing', clears) the
-- destination index name. Maps to
-- @PUT /_connector/{connector_id}/_index_name@.
updateConnectorIndexName ::
  ConnectorId ->
  UpdateConnectorIndexNameRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorIndexName cid body =
  put @StatusDependant (connectorPath cid ["_index_name"]) (encode body)

-- | 'updateConnectorNameDescription' updates the connector name and
-- description. Maps to @PUT /_connector/{connector_id}/_name@.
updateConnectorNameDescription ::
  ConnectorId ->
  UpdateConnectorNameDescriptionRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorNameDescription cid body =
  put @StatusDependant (connectorPath cid ["_name"]) (encode body)

-- | 'updateConnectorNative' toggles the @is_native@ flag. Maps to
-- @PUT /_connector/{connector_id}/_native@.
updateConnectorNative ::
  ConnectorId ->
  UpdateConnectorNativeRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorNative cid body =
  put @StatusDependant (connectorPath cid ["_native"]) (encode body)

-- | 'updateConnectorPipeline' updates the ingest pipeline. Maps to
-- @PUT /_connector/{connector_id}/_pipeline@.
updateConnectorPipeline ::
  ConnectorId ->
  UpdateConnectorPipelineRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorPipeline cid body =
  put @StatusDependant (connectorPath cid ["_pipeline"]) (encode body)

-- | 'updateConnectorScheduling' updates the scheduling. Maps to
-- @PUT /_connector/{connector_id}/_scheduling@.
updateConnectorScheduling ::
  ConnectorId ->
  UpdateConnectorSchedulingRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorScheduling cid body =
  put @StatusDependant (connectorPath cid ["_scheduling"]) (encode body)

-- | 'updateConnectorServiceType' updates the service type. Maps to
-- @PUT /_connector/{connector_id}/_service_type@.
updateConnectorServiceType ::
  ConnectorId ->
  UpdateConnectorServiceTypeRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorServiceType cid body =
  put @StatusDependant (connectorPath cid ["_service_type"]) (encode body)

-- | 'updateConnectorStatus' updates the status. Maps to
-- @PUT /_connector/{connector_id}/_status@.
updateConnectorStatus ::
  ConnectorId ->
  UpdateConnectorStatusRequest ->
  BHRequest StatusDependant ConnectorMutationResult
updateConnectorStatus cid body =
  put @StatusDependant (connectorPath cid ["_status"]) (encode body)

------------------------------------------------------------------------------
-- Connector sync jobs (/_connector/_sync_job/*)
------------------------------------------------------------------------------

-- | 'createConnectorSyncJob' creates a sync job. Maps to
-- @POST /_connector/_sync_job@. The server returns
-- 'ConnectorSyncJobCreateResponse' (a bare id — no @result@ field,
-- unlike connector create).
createConnectorSyncJob ::
  CreateConnectorSyncJobRequest ->
  BHRequest StatusDependant ConnectorSyncJobCreateResponse
createConnectorSyncJob body =
  post @StatusDependant ["_connector", "_sync_job"] (encode body)

-- | 'getConnectorSyncJob' fetches a sync job by id. Maps to
-- @GET /_connector/_sync_job/{connector_sync_job_id}@.
getConnectorSyncJob ::
  ConnectorSyncJobId ->
  BHRequest StatusDependant ConnectorSyncJob
getConnectorSyncJob jid = get @StatusDependant (syncJobPath jid [])

-- | 'listConnectorSyncJobs' lists sync jobs. Maps to
-- @GET /_connector/_sync_job@. Equivalent to
-- @'listConnectorSyncJobsWith' 'defaultConnectorSyncJobListOptions'@.
listConnectorSyncJobs ::
  BHRequest StatusDependant ConnectorSyncJobListResponse
listConnectorSyncJobs =
  listConnectorSyncJobsWith defaultConnectorSyncJobListOptions

-- | 'listConnectorSyncJobsWith' is the fully-parameterised form of
-- 'listConnectorSyncJobs'.
listConnectorSyncJobsWith ::
  ConnectorSyncJobListOptions ->
  BHRequest StatusDependant ConnectorSyncJobListResponse
listConnectorSyncJobsWith opts =
  get
    @StatusDependant
    ( mkEndpoint ["_connector", "_sync_job"]
        `withQueries` connectorSyncJobListOptionsParams opts
    )

-- | 'deleteConnectorSyncJob' deletes a sync job by id. Maps to
-- @DELETE /_connector/_sync_job/{connector_sync_job_id}@.
deleteConnectorSyncJob ::
  ConnectorSyncJobId ->
  BHRequest StatusDependant Acknowledged
deleteConnectorSyncJob jid =
  delete (syncJobPath jid [])

-- | 'cancelConnectorSyncJob' requests cancellation of a sync job. Maps
-- to @PUT /_connector/_sync_job/{id}/_cancel@ and returns
-- 'ConnectorMutationResult'.
cancelConnectorSyncJob ::
  ConnectorSyncJobId ->
  BHRequest StatusDependant ConnectorMutationResult
cancelConnectorSyncJob jid =
  put @StatusDependant (syncJobPath jid ["_cancel"]) emptyBody

-- | 'checkInConnectorSyncJob' records a sync-job heartbeat. Maps to
-- @PUT /_connector/_sync_job/{id}/_check_in@. The response schema is
-- undocumented, so it is returned as an opaque 'Value'.
checkInConnectorSyncJob ::
  ConnectorSyncJobId ->
  BHRequest StatusDependant Value
checkInConnectorSyncJob jid =
  put @StatusDependant (syncJobPath jid ["_check_in"]) emptyBody

-- | 'claimConnectorSyncJob' claims a sync job for a worker. Maps to
-- @PUT /_connector/_sync_job/{id}/_claim@. The response schema is
-- undocumented, so it is returned as an opaque 'Value'.
claimConnectorSyncJob ::
  ConnectorSyncJobId ->
  ClaimConnectorSyncJobRequest ->
  BHRequest StatusDependant Value
claimConnectorSyncJob jid body =
  put @StatusDependant (syncJobPath jid ["_claim"]) (encode body)

-- | 'setConnectorSyncJobError' sets the error on a sync job. Maps to
-- @PUT /_connector/_sync_job/{id}/_error@. The response schema is
-- undocumented, so it is returned as an opaque 'Value'.
setConnectorSyncJobError ::
  ConnectorSyncJobId ->
  SetConnectorSyncJobErrorRequest ->
  BHRequest StatusDependant Value
setConnectorSyncJobError jid body =
  put @StatusDependant (syncJobPath jid ["_error"]) (encode body)

-- | 'setConnectorSyncJobStats' updates the per-run counters of a sync
-- job. Maps to @PUT /_connector/_sync_job/{id}/_stats@. The response
-- schema is undocumented, so it is returned as an opaque 'Value'.
setConnectorSyncJobStats ::
  ConnectorSyncJobId ->
  SetConnectorSyncJobStatsRequest ->
  BHRequest StatusDependant Value
setConnectorSyncJobStats jid body =
  put @StatusDependant (syncJobPath jid ["_stats"]) (encode body)

------------------------------------------------------------------------------
-- Migration reindex (/_migration/reindex, /_create_from)
------------------------------------------------------------------------------

-- $migrationReindex
--
-- /Migration reindex/ endpoints (GA since ES 8.18.0, unchanged in 9.x)
-- upgrade the legacy backing indices of a data stream as part of a
-- major-version migration. Four operations:
--
-- * @POST /_migration/reindex@ — kicks off the background reindex
--   ('migrateReindex'); returns 'Acknowledged'.
-- * @POST /_migration/reindex/{index}/_cancel@ — cancels an in-progress
--   attempt ('cancelMigrateReindex'); returns 'Acknowledged'.
-- * @GET /_migration/reindex/{index}/_status@ — observes progress
--   ('getMigrateReindexStatus'); returns 'MigrateReindexStatus'.
-- * @POST /_create_from/{source}/{dest}@ — copies mappings and settings
--   from a source index into a fresh destination index
--   ('createIndexFrom' \/ 'createIndexFromWith'); returns
--   'CreateIndexFromResponse'.
--
-- The @{index}@ path segment accepts one or more comma-separated names
-- (the ES @_types.Indices@ type); the builders below take a 'NonEmpty'
-- 'IndexName' and render it comma-joined.

-- | Render a 'NonEmpty' 'IndexName' as the comma-joined @{index}@ path
-- segment used by the cancel and status endpoints.
renderMigrationReindexIndices :: NonEmpty IndexName -> Text
renderMigrationReindexIndices = T.intercalate "," . map unIndexName . toList

-- | 'migrateReindex' builds the 'BHRequest' for
-- @POST /_migration/reindex@ (ES 8.18+), which reindexes all legacy
-- backing indices of a data stream. The body is the encoded
-- 'MigrateReindexRequest' (use 'mkMigrateReindexRequest' to build the
-- canonical @upgrade@ request for a given data stream). The reindex runs
-- in a persistent task, so the endpoint returns 'Acknowledged'
-- immediately rather than a 'ReindexResponse'; poll
-- 'getMigrateReindexStatus' to observe progress.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-indices-migrate-reindex>.
migrateReindex ::
  MigrateReindexRequest ->
  BHRequest StatusDependant Acknowledged
migrateReindex req =
  post @StatusDependant endpoint (encode req)
  where
    endpoint = mkEndpoint ["_migration", "reindex"]

-- | 'cancelMigrateReindex' builds the 'BHRequest' for
-- @POST /_migration/reindex/{index}/_cancel@ (ES 8.18+), which cancels
-- an in-progress migration reindex attempt for the given data stream(s)
-- or index(es). Carries no body; returns 'Acknowledged' on success.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-indices-cancel-migrate-reindex>.
cancelMigrateReindex ::
  NonEmpty IndexName ->
  BHRequest StatusDependant Acknowledged
cancelMigrateReindex indices =
  post @StatusDependant endpoint emptyBody
  where
    endpoint =
      mkEndpoint
        ["_migration", "reindex", renderMigrationReindexIndices indices, "_cancel"]

-- | 'getMigrateReindexStatus' builds the 'BHRequest' for
-- @GET /_migration/reindex/{index}/_status@ (ES 8.18+), which reports the
-- status of a migration reindex attempt for the given data stream(s) or
-- index(es). Carries no body; the response is decoded into
-- 'MigrateReindexStatus'.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-indices-get-migrate-reindex-status>.
getMigrateReindexStatus ::
  NonEmpty IndexName ->
  BHRequest StatusDependant MigrateReindexStatus
getMigrateReindexStatus indices =
  get @StatusDependant endpoint
  where
    endpoint =
      mkEndpoint
        ["_migration", "reindex", renderMigrationReindexIndices indices, "_status"]

-- | 'createIndexFrom' builds the 'BHRequest' for
-- @POST /_create_from/{source}/{dest}@ (ES 8.18+), which copies the
-- mappings and settings from a source index into a fresh destination
-- index. Equivalent to @'createIndexFromWith' source dest Nothing@ — no
-- overrides, no body (the server applies its own defaults, including
-- @remove_index_blocks = true@). Returns 'CreateIndexFromResponse' on
-- success.
--
-- For more information see
-- <https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-indices-create-from>.
createIndexFrom ::
  IndexName ->
  IndexName ->
  BHRequest StatusDependant CreateIndexFromResponse
createIndexFrom source dest = createIndexFromWith source dest Nothing

-- | 'createIndexFromWith' is the fully-parameterised variant of
-- 'createIndexFrom', accepting an optional 'CreateIndexFromBody' whose
-- @mappings_override@ and @settings_override@ override the source
-- values. Pass 'Nothing' to send no body — wire-semantically equivalent
-- to 'createIndexFrom' (the server applies its own defaults, including
-- @remove_index_blocks = true@).
createIndexFromWith ::
  IndexName ->
  IndexName ->
  Maybe CreateIndexFromBody ->
  BHRequest StatusDependant CreateIndexFromResponse
createIndexFromWith source dest mBody =
  post @StatusDependant endpoint (maybe emptyBody encode mBody)
  where
    endpoint =
      mkEndpoint ["_create_from", unIndexName source, unIndexName dest]