//===----------------------------------------------------------------------===//
//                         DuckDB
//
// duckdb/storage/table/row_group.hpp
//
//
//===----------------------------------------------------------------------===//

#pragma once

#include "duckdb/storage/table/chunk_info.hpp"
#include "duckdb/storage/statistics/segment_statistics.hpp"
#include "duckdb/common/types/data_chunk.hpp"
#include "duckdb/common/enums/scan_options.hpp"
#include "duckdb/storage/table/per_column_metadata_blocks.hpp"
#include "duckdb/common/mutex.hpp"
#include "duckdb/parser/column_list.hpp"
#include "duckdb/storage/table/segment_base.hpp"
#include "duckdb/storage/block.hpp"
#include "duckdb/storage/storage_index.hpp"
#include "duckdb/storage/checkpoint/checkpoint_options.hpp"

namespace duckdb {
class AttachedDatabase;
class BlockManager;
class ColumnData;
class DatabaseInstance;
class DataTable;
class PartialBlockManager;
struct DataTableInfo;
class ExpressionExecutor;
class RowGroupCollection;
class RowGroupWriter;
class UpdateSegment;
class TableStatistics;
struct ColumnSegmentInfo;
class Vector;
struct ColumnCheckpointState;
struct PartitionStatistics;
struct PersistentColumnData;
struct PersistentRowGroupData;
struct RowGroupPointer;
struct TransactionData;
class CollectionScanState;
class TableFilter;
class TableFilterSet;
struct ColumnFetchState;
struct RowGroupAppendState;
class MetadataManager;
class RowVersionManager;
class ScanFilterInfo;
class StorageCommitState;
template <class T>
struct SegmentNode;
enum class ColumnDataType;

struct RowGroupWriteInfo {
	RowGroupWriteInfo(PartialBlockManager &manager, const vector<CompressionType> &compression_types,
	                  CheckpointOptions options = CheckpointOptions());
	RowGroupWriteInfo(PartialBlockManager &manager, const vector<CompressionType> &compression_types,
	                  vector<unique_ptr<PartialBlockManager>> &column_partial_block_managers_p);

private:
	PartialBlockManager &manager;

public:
	const vector<CompressionType> &compression_types;
	CheckpointOptions options;

public:
	PartialBlockManager &GetPartialBlockManager(idx_t column_idx);

private:
	optional_ptr<vector<unique_ptr<PartialBlockManager>>> column_partial_block_managers;
};

enum class RowGroupWriteAction {
	REUSE_EXISTING_ROW_GROUP_METADATA,
	PARTIALLY_REUSE_COLUMN_METADATA,
	FULLY_CHECKPOINT_ROW_GROUP
};

struct RowGroupWriteData {
	shared_ptr<RowGroup> result_row_group;
	vector<unique_ptr<ColumnCheckpointState>> states;
	vector<BaseStatistics> statistics;
	RowGroupWriteAction write_action = RowGroupWriteAction::FULLY_CHECKPOINT_ROW_GROUP;
	optional_idx write_count;
};

class RowGroup : public SegmentBase<RowGroup> {
public:
	friend class ColumnData;

public:
	RowGroup(RowGroupCollection &collection, idx_t count);
	RowGroup(RowGroupCollection &collection, RowGroupPointer pointer);
	RowGroup(RowGroupCollection &collection, PersistentRowGroupData &data);
	~RowGroup();

private:
	//! The RowGroupCollection this row-group is a part of
	reference<RowGroupCollection> collection;
	//! The version info of the row_group (inserted and deleted tuple info)
	atomic<optional_ptr<RowVersionManager>> version_info;
	//! The owned version info of the row_group (inserted and deleted tuple info)
	shared_ptr<RowVersionManager> owned_version_info;
	//! The column data of the row_group (mutable because `const` can lazily load)
	mutable vector<shared_ptr<ColumnData>> columns;

public:
	void MoveToCollection(RowGroupCollection &collection);
	RowGroupCollection &GetCollection() const {
		return collection.get();
	}
	//! Compute per-column metadata blocks by reading column metadata from disk
	PerColumnMetadataBlocks ComputePerColumnMetadataBlocks() const;

	const vector<MetaBlockPointer> &GetColumnStartPointers() const;

	vector<MetaBlockPointer> GetExtraMetadataBlockPointers() const;

	BlockManager &GetBlockManager() const;
	DataTableInfo &GetTableInfo() const;

	unique_ptr<RowGroup> AlterType(RowGroupCollection &collection, const LogicalType &target_type, idx_t changed_idx,
	                               ExpressionExecutor &executor, CollectionScanState &scan_state,
	                               SegmentNode<RowGroup> &node, DataChunk &scan_chunk);
	unique_ptr<RowGroup> AddColumn(RowGroupCollection &collection, ColumnDefinition &new_column,
	                               ExpressionExecutor &executor, Vector &intermediate);
	unique_ptr<RowGroup> RemoveColumn(RowGroupCollection &collection, idx_t removed_column);

	void CommitDrop();
	void CommitDropColumn(const idx_t column_index);

	void InitializeEmpty(const vector<LogicalType> &types, ColumnDataType data_type);
	bool HasChanges() const;

	//! Initialize a scan over this row_group
	bool InitializeScan(CollectionScanState &state, SegmentNode<RowGroup> &node);
	bool InitializeScanWithOffset(CollectionScanState &state, SegmentNode<RowGroup> &node, idx_t vector_offset);
	//! Checks the given set of table filters against the row-group statistics. Returns false if the entire row group
	//! can be skipped.
	bool CheckZonemap(ScanFilterInfo &filters);
	//! Checks the given set of table filters against the per-segment statistics. Returns false if any segments were
	//! skipped.
	bool CheckZonemapSegments(CollectionScanState &state);
	void Scan(ScanOptions options, CollectionScanState &state, DataChunk &result);
	void Scan(CollectionScanState &state, DataChunk &result, TableScanType type);

	idx_t GetSelVector(ScanOptions options, idx_t vector_idx, SelectionVector &sel_vector, idx_t max_count);

	//! For a specific row, returns true if it should be used for the transaction and false otherwise.
	bool Fetch(TransactionData transaction, idx_t row);
	//! Fetch a specific row from the row_group and insert it into the result at the specified index
	void FetchRow(TransactionData transaction, ColumnFetchState &state, const vector<StorageIndex> &column_ids,
	              row_t row_id, DataChunk &result, idx_t result_idx);

	//! Append count rows to the version info
	void AppendVersionInfo(TransactionData transaction, idx_t count);
	//! Commit a previous append made by RowGroup::AppendVersionInfo
	void CommitAppend(transaction_t commit_id, idx_t start, idx_t count);
	//! Revert a previous append made by RowGroup::AppendVersionInfo
	void RevertAppend(idx_t new_count);
	//! Clean up append states that can either be compressed or deleted
	void CleanupAppend(transaction_t lowest_transaction, idx_t start, idx_t count);

	//! Delete the given set of rows in the version manager
	idx_t Delete(TransactionData transaction, DataTable &table, row_t *row_ids, idx_t count, idx_t row_group_start);

	static vector<RowGroupWriteData> WriteToDisk(RowGroupWriteInfo &info,
	                                             const vector<const_reference<RowGroup>> &row_groups);
	//! Write the data inside this RowGroup to disk and return a struct with information about the write
	//! Including the new RowGroup that contains the columns in their written-to-disk form
	RowGroupWriteData WriteToDisk(RowGroupWriteInfo &info) const;
	//! Returns the number of committed rows (count - committed deletes)
	idx_t GetCommittedRowCount();
	//! Returns the number of rows visible to the given transaction
	idx_t GetVisibleRowCount(TransactionData transaction);
	bool CanReuseMetadata(RowGroupWriter &writer) const;
	RowGroupWriteData WriteToDisk(RowGroupWriter &writer);
	RowGroupPointer Checkpoint(RowGroupWriteData write_data, RowGroupWriter &writer, TableStatistics &global_stats,
	                           idx_t row_group_start);
	bool IsPersistent() const;
	PersistentRowGroupData SerializeRowGroupInfo(idx_t row_group_start) const;

	void InitializeAppend(RowGroupAppendState &append_state);
	void Append(RowGroupAppendState &append_state, DataChunk &chunk, idx_t append_count);

	void Update(TransactionData transaction, DataTable &data_table, DataChunk &updates, row_t *ids, idx_t offset,
	            idx_t count, const vector<PhysicalIndex> &column_ids, idx_t row_group_start);
	//! Update a single column; corresponds to DataTable::UpdateColumn
	//! This method should only be called from the WAL
	void UpdateColumn(TransactionData transaction, DataTable &data_table, DataChunk &updates, Vector &row_ids,
	                  idx_t offset, idx_t count, const vector<column_t> &column_path, idx_t row_group_start);

	void MergeStatistics(idx_t column_idx, const BaseStatistics &other);
	void MergeIntoStatistics(idx_t column_idx, BaseStatistics &other);
	void MergeIntoStatistics(TableStatistics &other);
	unique_ptr<BaseStatistics> GetStatistics(idx_t column_idx) const;
	unique_ptr<BaseStatistics> GetStatistics(const StorageIndex &column_idx) const;

	void GetColumnSegmentInfo(const QueryContext &context, idx_t row_group_index, vector<ColumnSegmentInfo> &result);
	static PartitionStatistics GetPartitionStats(SegmentNode<RowGroup> &row_group);

	idx_t GetAllocationSize() const {
		return allocation_size;
	}

	void Verify();

	void NextVector(CollectionScanState &state);

	idx_t DeleteRows(idx_t vector_idx, transaction_t transaction_id, row_t rows[], idx_t count);
	RowVersionManager &GetOrCreateVersionInfo();

	// Serialization
	static void Serialize(RowGroupPointer &pointer, Serializer &serializer, bool supports_per_column_writes);
	static RowGroupPointer Deserialize(Deserializer &deserializer);

	idx_t GetRowGroupSize() const;

	static FilterPropagateResult CheckRowIdFilter(const TableFilter &filter, idx_t beg_row, idx_t end_row);
	idx_t GetColumnCount() const;

	vector<MetaBlockPointer> CheckpointDeletes(RowGroupWriter &writer);

	//! Direct accessors, fall outside of general use but can be useful to some extensions
	ColumnData &GetRawColumnData(const StorageIndex &c) const;
	ColumnData &GetRawColumnData(storage_t c) const;

private:
	optional_ptr<RowVersionManager> GetVersionInfo();
	optional_ptr<RowVersionManager> GetVersionInfoIfLoaded() const;
	shared_ptr<RowVersionManager> GetOrCreateVersionInfoPtr();
	shared_ptr<RowVersionManager> GetOrCreateVersionInfoInternal();
	void SetVersionInfo(shared_ptr<RowVersionManager> version);

	ColumnData &GetColumn(storage_t c) const;
	void LoadColumn(storage_t c) const;
	ColumnData &GetColumn(const StorageIndex &c) const;
	vector<shared_ptr<ColumnData>> &GetColumns();
	void LoadRowIdColumnData() const;
	void SetCount(idx_t count);
	bool ColumnIsLoaded(storage_t c) const;
	void UnloadColumn(storage_t c);
	bool HasUnchangedColumns() const;
	static shared_ptr<ColumnData> CheckpointColumn(const RowGroup &row_group, idx_t column_idx, RowGroupWriteInfo &info,
	                                               RowGroupWriteData &write_data);

	bool HasUnloadedDeletes() const;
	unique_ptr<RowGroup> CreateNewRowGroupCopy(RowGroupCollection &new_collection, idx_t new_column_count);

private:
	mutable mutex row_group_lock;
	vector<MetaBlockPointer> column_pointers;
	//! Whether or not each column is loaded (mutable because `const` can lazy load)
	mutable unique_ptr<atomic<bool>[]> is_loaded;
	vector<MetaBlockPointer> deletes_pointers;
	bool has_metadata_blocks = false;
	vector<idx_t> extra_metadata_blocks;
	bool has_per_column_metadata_blocks = false;
	PerColumnMetadataBlocks per_column_metadata_blocks;
	atomic<bool> deletes_is_loaded;
	atomic<idx_t> allocation_size;
	//! The row id column data (mutable because `const` can lazy load)
	mutable unique_ptr<ColumnData> row_id_column_data;
	//! Whether or not `row_id_column_data` is loaded (mutable because `const` can lazy load)
	mutable atomic<bool> row_id_is_loaded;
	atomic<bool> has_changes;
};

} // namespace duckdb
