/*-------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 *
 * include/metadata/index.h
 *
 * Accessors around ApiCatalogSchema.collection_indexes.
 *
 *-------------------------------------------------------------------------
 */
#ifndef INDEXES_H
#define INDEXES_H

#include <postgres.h>
#include <utils/array.h>

#include "io/bson_core.h"
#include "background_worker/background_worker_job.h"

#define INVALID_INDEX_ID ((int) 0)

/* arg1: ApiCatalogSchema.collection_indexes.index_id */
#define DOCUMENT_DATA_TABLE_INDEX_NAME_FORMAT_PREFIX "documents_rum_index_"
#define DOCUMENT_DATA_TABLE_INDEX_NAME_FORMAT \
	DOCUMENT_DATA_TABLE_INDEX_NAME_FORMAT_PREFIX "%d"

#define DOCUMENT_DATA_PRIMARY_KEY_FORMAT_PREFIX "collection_pk_"

#define ID_FIELD_KEY "_id"
#define ID_INDEX_NAME "_id_"
#define CREATE_INDEX_COMMAND_TYPE 'C'
#define REINDEX_COMMAND_TYPE 'R'

#define IndexRequestKey "indexRequest"
#define IndexRequestKeyLength 12
#define CmdTypeKey "cmdType"
#define CmdTypeKeyLength 7
#define IdsKey "ids"
#define IdsKeyLength 3

extern int MaxNumActiveUsersIndexBuilds;

typedef enum BoolIndexOption
{
	BoolIndexOption_Undefined = 0,

	BoolIndexOption_False = 1,

	BoolIndexOption_True = 2,
} BoolIndexOption;

/*
 * CustomIndexOption extends BoolIndexOption with an additional DefaultTrue state
 * to distinguish system-managed defaults from user-explicit settings.
 *
 * Undefined is 0 so that zero-initialized structs (palloc0) default to
 * "not specified" rather than "explicitly false", which would have added an
 * implicit contract that composite options needed to be set as undefined after init.
 */
typedef enum CustomIndexOption
{
	/*
	 *  User did not specify the option. Zero-init safe. Not persisted in index option.
	 *  Before CustomIndexOption: Undefined undefined or explicit false
	 *  After CustomIndexOption: Undefined means undefined
	 */
	CustomIndexOption_Undefined = 0,

	/* User explicitly set the option to false. Persisted in index option as -1.*/
	CustomIndexOption_False = -1,

	/* System forced the option to true (e.g. TTL GUC, defaultUseCompositeOpClass).
	 * Persisted in index option.*/
	CustomIndexOption_DefaultTrue = 1,

	/*
	 *  User explicitly set the option to true. Persisted in index option as 2.
	 *  After CustomIndexOption: 2 is explicit true, set by user.
	 */
	CustomIndexOption_True = 2,
} CustomIndexOption;

/*
 * Representation of index_spec_type.
 *
 * If you decide making any changes to this struct, consider syncing
 * following functions:
 *  - DatumGetIndexSpec()
 *  - IndexSpecGetDatum()
 *  - CopyIndexSpec()
 *  - IndexSpecOptionsAreEquivalent()
 *  - MakeIndexSpecForIndexDef()
 * , and index_spec_type_internal type in documentdb_api.sql.
 */
typedef struct IndexSpec
{
	/* index name, cannot be NULL */
	char *indexName;

	/** index options start here **/

	/* index version specified by "v" number. must be a positive number */
	int indexVersion;

	/* "key" document, cannot be NULL */
	pgbson *indexKeyDocument;

	/* "partialFilterExpression" document, allowed to be NULL */
	pgbson *indexPFEDocument;

	/* normalized form of "wildcardProjection" document, allowed to be NULL */
	pgbson *indexWPDocument;

	/* whether it's "sparse" or undefined */
	BoolIndexOption indexSparse;

	/* whether it's "unique" or undefined */
	BoolIndexOption indexUnique;

	/* document expiry time for TTL index, NULL means it's not specified */
	int *indexExpireAfterSeconds;

	/* Options pertaining to cosmosSearch: TODO: Integrate with indexOptions */
	pgbson *cosmosSearchOptions;

	/* General index options - new options should be appended here. */
	pgbson *indexOptions;
} IndexSpec;

/* A set of text indexing weights for text indexes */
typedef struct TextIndexWeights
{
	/* The path in the doc to index */
	const char *path;

	/* The weight associated with the path */
	double weight;
} TextIndexWeights;

/*
 * Metadata about indexes that can be queried from the index metadata API.
 */
typedef struct IndexDetails
{
	/* index id assigned to this Mongo index */
	int indexId;

	/* collection id of a collection to which this Mongo index belongs*/
	uint64 collectionId;

	/* Mongo index spec */
	IndexSpec indexSpec;

	/* Whether or not the index build is in progress (background build) */
	bool isIndexBuildInProgress;

	/* Whether or not the index is valid */
	bool isIndexValid;
} IndexDetails;

/*
 * Index command status in ApiCatalogSchemaName.{ExtensionObjectPrefix}_index_queue
 * Since GetRequestFromIndexQueue relies on order of index_cmd_status to avoid the starvation
 * the enum values are kept in such a way that GetRequestFromIndexQueue always picks request in order
 * IndexCmdStatus_Queued first then IndexCmdStatus_Failed (ascending order).
 */
typedef enum IndexCmdStatus
{
	IndexCmdStatus_Unknown = 0,

	IndexCmdStatus_Queued = 1,

	IndexCmdStatus_Inprogress = 2,

	IndexCmdStatus_Failed = 3,

	IndexCmdStatus_Skippable = 4,
} IndexCmdStatus;

/*
 * Index command request. The command can be of CREATE INDEX/DROP INDEX.
 */
typedef struct IndexCmdRequest
{
	/* Postgres command */
	char *cmd;

	/* index id assigned to this index */
	int indexId;

	/* collection id of a collection to which this index belongs*/
	uint64 collectionId;

	/* Internal retry attempt count that we maintain for each request*/
	int16 attemptCount;

	/* comment from previous attempt*/
	pgbson *comment;

	/* Denotes the time when request was updated in the extension_index_queue*/
	TimestampTz updateTime;

	IndexCmdStatus status;

	/* Denotes the user who has triggered CreateIndexes*/
	Oid userOid;

	/* The cmdType for this request */
	char cmdType;
} IndexCmdRequest;

/* Return value of GetIndexBuildJobOpId */
typedef struct
{
	/* timestamp for query start time from pg_stat_activity*/
	TimestampTz start_time;

	/* global_pid from pg_stat_activity*/
	int64 global_pid;
} IndexJobOpId;

/*
 * Represents the type of index for a given path.
 * Treat this as a flags so that we can check for
 * plugins.
 */
typedef enum MongoIndexKind
{
	/* Unrecognized or unsupported index plugin */
	MongoIndexKind_Unknown = 0x0,

	/* Regular asc/desc index */
	MongoIndexKind_Regular = 0x1,

	/* Hashed index */
	MongoIndexKind_Hashed = 0x2,

	/* Geospatial 2D index */
	MongoIndexKind_2d = 0x4,

	/* Text search index */
	MongoIndexKind_Text = 0x8,

	/* Geospatial 2D index */
	MongoIndexKind_2dsphere = 0x10,

	/* A vector index kind */
	MongoIndexKind_CosmosSearch = 0x20,

	/* Preserved for extended index kinds */
	MongoIndexKind_Extended = 0x100,
} MongoIndexKind;

typedef struct
{
	const char *mongoIndexName;
	bool isSupported;
	MongoIndexKind indexKind;
} MongoIndexSupport;

MongoIndexKind GetMongoIndexKind(char *indexKindName, bool *isSupported);

/* query index metadata */
IndexDetails * FindIndexWithSpecOptions(uint64 collectionId,
										const IndexSpec *targetIndexSpec);
IndexDetails * IndexIdGetIndexDetails(int indexId);
IndexDetails * IndexIdGetIndexDetailsWithCurrentTxns(int indexId);
IndexDetails * IndexNameGetIndexDetails(uint64 collectionId, const char *indexName);
IndexDetails * IndexNameGetReadyIndexDetails(uint64 collectionId, const char *indexName);
List * IndexKeyGetMatchingIndexes(uint64 collectionId,
								  const pgbson *indexKeyDocument);
List * IndexKeyGetReadyMatchingIndexes(uint64 collectionId,
									   const pgbson *indexKeyDocument);
List * CollectionIdGetIndexes(uint64 collectionId, bool excludeIdIndex,
							  bool enableNestedDistribution);
List * CollectionIdGetValidIndexes(uint64 collectionId, bool excludeIdIndex,
								   bool enableNestedDistribution);
List * CollectionIdGetIndexNames(uint64 collectionId, bool excludeIdIndex, bool
								 inProgressOnly);
int CollectionIdGetIndexCount(uint64 collectionId);
int CollectionIdsGetIndexCount(ArrayType *collectionIdsArray);
bool IndexSpecIsWildcardIndex(const IndexSpec *indexSpec);
bool IndexSpecIsOrderedIndex(const IndexSpec *indexSpec);


/* modify/write index metadata */
int RecordCollectionIndex(uint64 collectionId, const IndexSpec *indexSpec,
						  bool isKnownValid);
int MarkIndexesAsValid(uint64 collectionId, const List *indexIdList);
void DeleteAllCollectionIndexRecords(uint64 collectionId);
void DeleteCollectionIndexRecord(uint64 collectionId, int indexId);

List * MergeTextIndexWeights(List *textIndexes, const bson_value_t *weights,
							 bool *isWildCard, bool includeWildCardInWeights);

/*
 * An equivalency for Index Specifications.
 */
typedef enum IndexOptionsEquivalency
{
	/*
	 * The index specifications are not equivalent
	 * (They are different)
	 */
	IndexOptionsEquivalency_NotEquivalent,

	/*
	 * The index specifications are equivalent from
	 * an options being compatible with each other
	 * and should be treated as indexes that match
	 * each other but are actually different indexes
	 */
	IndexOptionsEquivalency_Equivalent,

	/*
	 *  We allow only one text index per collection.
	 *  So, all subsequent text indexes are equivalent,
	 *  but we would need to throw a different error message.
	 */
	IndexOptionsEquivalency_TextEquivalent,

	/*
	 * The index specs are actually the same.
	 */
	IndexOptionsEquivalency_Equal,

	/*
	 * The index specs are treated as equal for migration compatibility
	 * purposes. For example, existing is an unordered index and incoming
	 * is an implicit ordered index. They are treated as equivalent to preserve
	 * existing behavior but we should not report their options to be
	 * equivalent.
	 */
	IndexOptionsEquivalency_CompatEqual,
} IndexOptionsEquivalency;

/* public helpers for IndexSpec */
IndexOptionsEquivalency IndexSpecOptionsAreEquivalent(const IndexSpec *leftIndexSpec,
													  const IndexSpec *rightIndexSpec);
bool IndexSpecTTLOptionsAreSame(const IndexSpec *leftIndexSpec,
								const IndexSpec *rightIndexSpec);
pgbson * IndexSpecAsBson(const IndexSpec *indexSpec);

/* utilities for index_spec_type and IndexSpec objects */
BoolIndexOption BoolDatumGetBoolIndexOption(bool datumIsNull, Datum datum);
IndexSpec * DatumGetIndexSpec(Datum datum);
IndexSpec MakeIndexSpecForBuiltinIdIndex(void);
Datum IndexSpecGetDatum(IndexSpec *indexSpec);
IndexSpec * CopyIndexSpec(const IndexSpec *indexSpec);
void WriteIndexSpecAsCurrentOpCommand(pgbson_writer *finalWriter, const
									  char *databaseName,
									  const char *collectionName, const
									  IndexSpec *indexSpec);
void RemoveRequestFromIndexQueue(int indexId, char cmdType);
void MarkIndexRequestStatus(int indexId, char cmdType, IndexCmdStatus status,
							pgbson *comment,
							IndexJobOpId *opId, int16 attemptCount);
IndexCmdStatus GetIndexBuildStatusFromIndexQueue(int indexId);
IndexCmdRequest * GetRequestFromIndexQueue(uint64 collectionId,
										   MemoryContext mcxt);
IndexCmdRequest * GetSkippableRequestFromIndexQueue(int expireTimeInSeconds,
													List *skipCollections,
													MemoryContext mcxt);
List * GetInProgressRequestFromIndexQueue(uint64 collectionId,
										  MemoryContext mcxt);
uint64 * GetCollectionIdsForIndexBuild(List *excludeCollectionIds);
void AddRequestInIndexQueue(char *createIndexCmd, int indexId, uint64 collectionId, char
							cmd_type, Oid userOid);
char * GetIndexQueueName(void);
char * GetIndexQueueTableName(void);
void CreateIndexQueueIfNotExists(bool includeOptions, bool includeDropCommandType);
const char * GetIndexTypeFromKeyDocument(pgbson *keyDocument);

void UpdateIndexStatisticsForPlannerStatistics(uint64 collectionId, List *indexIdList);
void UpdateCollectionPlannerStatistics(uint64 collectionId, bool enableStats);
void DropIndexStatisticsForPlannerStatistics(uint64 collectionId, List *indexIdList);
bool CollectionHasStatisticsEnabled(uint64 collectionId);

/* Static utilities */

static inline bool
GetBoolFromBoolIndexOptionDefaultTrue(BoolIndexOption option)
{
	return option == BoolIndexOption_Undefined ||
		   option == BoolIndexOption_False ? false : true;
}


static inline bool
GetBoolFromBoolIndexOptionDefaultFalse(BoolIndexOption option)
{
	return option == BoolIndexOption_True;
}


#endif
