/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "arrow/array.h"
#include "arrow/compute/api.h"
#include "arrow/dataset/file_parquet.h"
#include "arrow/io/caching.h"
#include "arrow/record_batch.h"
#include "arrow/type.h"
#include "arrow/type_fwd.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/format/parquet/row_ranges.h"
#include "paimon/format/parquet/target_row_group.h"
#include "paimon/result.h"
#include "paimon/status.h"
#include "parquet/arrow/reader.h"
#include "parquet/page_index.h"

namespace arrow {
class Schema;
}  // namespace arrow

namespace paimon {
class Predicate;
}  // namespace paimon

namespace paimon::parquet {

// The FileReaderWrapper is a decorator class designed to support seek functionality, as well as the
// methods GetPreviousBatchFirstRowNumber and GetNextRowToRead.
class FileReaderWrapper {
 public:
    ~FileReaderWrapper();

    static Result<std::unique_ptr<FileReaderWrapper>> Create(
        std::unique_ptr<::parquet::arrow::FileReader>&& reader, int64_t batch_size,
        std::shared_ptr<arrow::MemoryPool> pool);

    /// Seek to the specified row number.
    /// @param row_number The row to seek to (must be at a row group boundary).
    Status SeekToRow(uint64_t row_number);

    /// Read the next batch of rows.
    /// @return The next RecordBatch, or nullptr if end of data.
    Result<std::shared_ptr<arrow::RecordBatch>> Next();

    /// Get the first row number of the previously returned batch. After Next() reaches EOF,
    /// returns the next unread row number (the end of the readable range).
    Result<uint64_t> GetPreviousBatchFirstRowNumber() const {
        return previous_first_row_;
    }

    /// Get the row number that will be read next.
    uint64_t GetNextRowToRead() const {
        return next_row_to_read_;
    }

    /// Get the total number of rows in the file.
    uint64_t GetNumberOfRows() const {
        return num_rows_;
    }

    /// Get the number of row groups in the file.
    int32_t GetNumberOfRowGroups() const {
        return file_reader_->num_row_groups();
    }

    /// Get the underlying Parquet file reader.
    ::parquet::arrow::FileReader* GetFileReader() {
        return file_reader_.get();
    }

    /// Get the [start, end) ranges for all row groups.
    const std::vector<std::pair<uint64_t, uint64_t>>& GetAllRowGroupRanges() const {
        return all_row_group_ranges_;
    }

    /// Get the Arrow schema of the file.
    Result<std::shared_ptr<arrow::Schema>> GetSchema() const;

    /// Close the batch reader and release resources.
    Status Close();

    /// Get the [start, end) ranges for the specified row groups.
    /// @param row_group_indices The row group indices to get ranges for.
    Result<std::vector<std::pair<uint64_t, uint64_t>>> GetRowGroupRanges(
        const std::set<int32_t>& row_group_indices) const;

    /// Prepare for lazy reading of the specified row groups and columns.
    /// Actual reader initialization is deferred until the first Next() call.
    Status PrepareForReadingLazy(const std::vector<TargetRowGroup>& target_row_groups,
                                 const std::vector<int32_t>& column_indices);

    /// Prepare for immediate reading of the specified row groups and columns.
    /// Initializes the reader and starts pre-buffering I/O.
    ///
    /// Note: when the read schema has nested sub-field projection,
    /// page-level filtering is disabled temporarily due to known offset
    /// calculation issues for nested pages.
    Status PrepareForReading(const std::vector<TargetRowGroup>& target_row_groups,
                             const std::vector<int32_t>& column_indices);

    /// Apply read ranges to the current target_row_groups_, keeping only those
    /// whose row-group range is equal to one of the given read ranges.
    /// Resets reader state so that the next Next() call will re-initialize.
    Status ApplyReadRanges(const std::vector<std::pair<uint64_t, uint64_t>>& read_ranges);

    /// Get the page index reader for the file.
    /// Returns nullptr if page index is not available.
    std::shared_ptr<::parquet::PageIndexReader> GetPageIndexReader();

    /// Calculate filtered row ranges for a row group based on predicate.
    /// @param row_group_index The row group index.
    /// @param predicate The predicate to evaluate.
    /// @param column_name_to_index Map from column name to column index.
    /// @return RowRanges that may contain matching rows.
    Result<RowRanges> CalculateFilteredRowRanges(
        int32_t row_group_index, const std::shared_ptr<Predicate>& predicate,
        const std::map<std::string, int32_t>& column_name_to_index);

    /// Get or create the page index reader for a row group.
    std::shared_ptr<::parquet::RowGroupPageIndexReader> GetRowGroupPageIndexReader(
        int32_t row_group_index);

 private:
    FileReaderWrapper(std::unique_ptr<::parquet::arrow::FileReader>&& file_reader,
                      const std::vector<std::pair<uint64_t, uint64_t>>& all_row_group_ranges,
                      uint64_t num_rows, int64_t batch_size,
                      std::shared_ptr<::arrow::MemoryPool> pool);

    /// Wait for all pending PreBuffer operations to complete.
    void WaitForPendingPreBuffer();

    /// Advance current_row_group_idx_ to the next row group and update next_row_to_read_.
    void AdvanceToNextRowGroup();

    /// Read next batch from a page-filtered row group. Returns nullptr when the RG is exhausted.
    Result<std::shared_ptr<arrow::RecordBatch>> NextPageFiltered();

    /// Read next batch from the fully-matched batch_reader_. Returns nullptr when exhausted.
    Result<std::shared_ptr<arrow::RecordBatch>> NextFullyMatched();

    /// Collect all byte ranges that need pre-buffering (page-filtered + fully-matched).
    std::vector<::arrow::io::ReadRange> CollectPreBufferRanges(
        const std::vector<int32_t>& column_indices);

    /// Dispatch a single PreBufferRanges call with merged ranges.
    void DispatchPreBuffer(std::vector<::arrow::io::ReadRange> ranges);

    std::unique_ptr<::parquet::arrow::FileReader> file_reader_;
    std::unique_ptr<arrow::RecordBatchReader> batch_reader_;

    std::vector<std::pair<uint64_t, uint64_t>> all_row_group_ranges_;
    std::vector<int32_t> target_column_indices_;

    std::shared_ptr<::arrow::MemoryPool> pool_;
    int64_t batch_size_;  // 0 means no limit

    const uint64_t num_rows_;
    uint64_t next_row_to_read_ = std::numeric_limits<uint64_t>::max();
    uint64_t previous_first_row_ = std::numeric_limits<uint64_t>::max();
    uint64_t current_row_group_idx_ = 0;
    bool reader_initialized_ = false;

    // Streaming reader for the currently-active page-filtered row group. Created lazily
    // on the first Next() call into a page-filtered RG, drained batch-by-batch, then reset
    // when ReadNext returns nullptr (end of that RG).
    std::unique_ptr<arrow::RecordBatchReader> current_page_filtered_reader_;
    int64_t filtered_global_offset_ = 0;      // Cumulative filtered-row offset within RG
    RowRanges current_filtered_row_ranges_;   // RowRanges for the active page-filtered RG
    uint64_t current_filtered_rg_start_ = 0;  // Absolute row-group start row number

    // Target row groups with row ranges for none page-level filtering and page-level filtering
    std::vector<TargetRowGroup> target_row_groups_;

    // Track pre-buffered ranges so we can wait on destruction
    std::vector<::arrow::io::ReadRange> prebuffered_ranges_;

    // Arrow caches the file-level PageIndexReader, but RowGroup() creates a new reader each time.
    // Keep one reader per row group so its page-index buffers are shared by all read stages.
    std::map<int32_t, std::shared_ptr<::parquet::RowGroupPageIndexReader>>
        row_group_page_index_readers_;
};

}  // namespace paimon::parquet
