// -------------------------------------------------------------------------------------------------
//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
//  https://nautechsystems.io
//
//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
//  You may not use this file except in compliance with the License.
//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
//
//  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.
// -------------------------------------------------------------------------------------------------

//! Builder types for Bybit REST query parameters and filters.

use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use crate::common::{
    enums::{
        BybitAccountType, BybitExecType, BybitInstrumentStatus, BybitKlineInterval,
        BybitMarginMode, BybitMarketUnit, BybitOpenOnly, BybitOptionType, BybitOrderFilter,
        BybitOrderSide, BybitOrderStatus, BybitOrderType, BybitPositionIdx, BybitPositionMode,
        BybitProductType, BybitSmpType, BybitStopOrderType, BybitTimeInForce, BybitTpSlMode,
        BybitTriggerDirection, BybitTriggerType,
    },
    parse::opt_bool_as_int,
};

/// Query parameters for `GET /v5/market/instruments-info`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/instrument>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitInstrumentsInfoParams {
    pub category: BybitProductType,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub symbol: Option<String>,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<BybitInstrumentStatus>,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_coin: Option<String>,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cursor: Option<String>,
}

/// Query parameters for `GET /v5/market/tickers`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/tickers>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitTickersParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub exp_date: Option<String>,
}

/// Query parameters for `GET /v5/market/kline`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/kline>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(setter(into, strip_option), default)]
pub struct BybitKlinesParams {
    pub category: BybitProductType,
    pub symbol: String,
    pub interval: BybitKlineInterval,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl Default for BybitKlinesParams {
    fn default() -> Self {
        Self {
            category: BybitProductType::Linear,
            symbol: String::new(),
            interval: BybitKlineInterval::Minute1,
            start: None,
            end: None,
            limit: None,
        }
    }
}

/// Query parameters for `GET /v5/market/recent-trade`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/recent-trade>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(setter(into, strip_option), default)]
pub struct BybitTradesParams {
    pub category: BybitProductType,
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub option_type: Option<BybitOptionType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl Default for BybitTradesParams {
    fn default() -> Self {
        Self {
            category: BybitProductType::Linear,
            symbol: String::new(),
            base_coin: None,
            option_type: None,
            limit: None,
        }
    }
}

/// Query parameters for `GET /v5/market/funding/history`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/history-fund-rate>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(setter(into, strip_option), default)]
pub struct BybitFundingParams {
    pub category: BybitProductType,
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl Default for BybitFundingParams {
    fn default() -> Self {
        Self {
            category: BybitProductType::Linear,
            symbol: String::new(),
            start_time: None,
            end_time: None,
            limit: None,
        }
    }
}

/// Query parameters for `GET /v5/market/orderbook`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/market/orderbook>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[builder(setter(into, strip_option), default)]
pub struct BybitOrderbookParams {
    pub category: BybitProductType,
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl Default for BybitOrderbookParams {
    fn default() -> Self {
        Self {
            category: BybitProductType::Linear,
            symbol: String::new(),
            limit: None,
        }
    }
}

/// Query parameters for `GET /v5/asset/coin/query-info`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/asset/coin-info>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitCoinInfoParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub coin: Option<String>,
}

/// Query parameters for `GET /v5/account/fee-rate`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/account/fee-rate>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitFeeRateParams {
    pub category: BybitProductType,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub symbol: Option<String>,
    #[builder(setter(strip_option))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub base_coin: Option<String>,
}

/// Query parameters for `GET /v5/account/wallet-balance`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/account/wallet-balance>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitWalletBalanceParams {
    pub account_type: BybitAccountType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub coin: Option<String>,
}

/// Query parameters for `GET /v5/position/list`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/position>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitPositionListParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub settle_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub cursor: Option<String>,
}

/// Body parameters for `POST /v5/account/set-margin-mode`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/account/set-margin-mode>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitSetMarginModeParams {
    pub set_margin_mode: BybitMarginMode,
}

/// Body parameters for `POST /v5/position/set-leverage`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/position/leverage>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitSetLeverageParams {
    pub category: BybitProductType,
    pub symbol: String,
    pub buy_leverage: String,
    pub sell_leverage: String,
}

/// Body parameters for `POST /v5/position/switch-mode`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/position/position-mode>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitSwitchModeParams {
    pub category: BybitProductType,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub coin: Option<String>,
    pub mode: BybitPositionMode,
}

/// Body parameters for `POST /v5/position/trading-stop`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/position/trading-stop>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitSetTradingStopParams {
    pub category: BybitProductType,
    pub symbol: String,
    pub position_idx: BybitPositionIdx,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub take_profit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_loss: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trailing_stop: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_trigger_by: Option<BybitTriggerType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_trigger_by: Option<BybitTriggerType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub active_price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tpsl_mode: Option<BybitTpSlMode>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_size: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_size: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_limit_price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_limit_price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_order_type: Option<BybitOrderType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_order_type: Option<BybitOrderType>,
}

/// Body parameters for `POST /v5/account/borrow`.
///
/// # References
///
/// - <https://bybit-exchange.github.io/docs/v5/account/borrow>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBorrowParams {
    pub coin: String,
    pub amount: String,
}

/// Body parameters for `POST /v5/account/no-convert-repay`.
///
/// # References
///
/// - <https://bybit-exchange.github.io/docs/v5/account/no-convert-repay>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitNoConvertRepayParams {
    pub coin: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option), default)]
    pub amount: Option<String>,
}

/// Order entry payload for `POST /v5/order/create-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-place>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchPlaceOrderEntry {
    pub symbol: String,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_leverage: Option<i32>,
    pub side: BybitOrderSide,
    pub order_type: BybitOrderType,
    pub qty: String,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub price: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub market_unit: Option<BybitMarketUnit>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_direction: Option<BybitTriggerDirection>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub order_filter: Option<BybitOrderFilter>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_price: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_by: Option<BybitTriggerType>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub order_iv: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_in_force: Option<BybitTimeInForce>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub position_idx: Option<BybitPositionIdx>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub take_profit: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_loss: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_trigger_by: Option<BybitTriggerType>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_trigger_by: Option<BybitTriggerType>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reduce_only: Option<bool>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub close_on_trigger: Option<bool>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub smp_type: Option<BybitSmpType>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mmp: Option<bool>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tpsl_mode: Option<BybitTpSlMode>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_limit_price: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_limit_price: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_order_type: Option<BybitOrderType>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_order_type: Option<BybitOrderType>,
}

/// Body parameters for `POST /v5/order/create-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-place>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchPlaceOrderParams {
    pub category: BybitProductType,
    pub request: Vec<BybitBatchPlaceOrderEntry>,
}

/// Body parameters for `POST /v5/order/create`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/create-order>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitPlaceOrderParams {
    #[serde(flatten)]
    pub order: BybitBatchPlaceOrderEntry,
    pub category: BybitProductType,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slippage_tolerance_type: Option<String>,
    #[builder(default)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slippage_tolerance: Option<String>,
}

/// Amend entry for `POST /v5/order/amend-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-amend>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchAmendOrderEntry {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub order_iv: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub qty: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tpsl_mode: Option<BybitTpSlMode>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub take_profit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_loss: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_trigger_by: Option<BybitTriggerType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_trigger_by: Option<BybitTriggerType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_by: Option<BybitTriggerType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tp_limit_price: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sl_limit_price: Option<String>,
}

/// Body parameters for `POST /v5/order/amend-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-amend>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchAmendOrderParams {
    pub category: BybitProductType,
    pub request: Vec<BybitBatchAmendOrderEntry>,
}

/// Body parameters for `POST /v5/order/amend`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/amend-order>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitAmendOrderParams {
    #[serde(flatten)]
    pub order: BybitBatchAmendOrderEntry,
    pub category: BybitProductType,
}

/// Cancel entry for `POST /v5/order/cancel-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-cancel>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchCancelOrderEntry {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub order_filter: Option<BybitOrderFilter>,
}

/// Body parameters for `POST /v5/order/cancel-batch`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/batch-cancel>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitBatchCancelOrderParams {
    pub category: BybitProductType,
    pub request: Vec<BybitBatchCancelOrderEntry>,
}

/// Body parameters for `POST /v5/order/cancel`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/cancel-order>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
pub struct BybitCancelOrderParams {
    #[serde(flatten)]
    pub order: BybitBatchCancelOrderEntry,
    pub category: BybitProductType,
}

/// Body parameters for `POST /v5/order/cancel-all`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/cancel-all>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitCancelAllOrdersParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub settle_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_filter: Option<BybitOrderFilter>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub stop_order_type: Option<BybitStopOrderType>,
}

/// Query parameters for `GET /v5/order/realtime`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/open-order>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitOpenOrdersParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub settle_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub open_only: Option<BybitOpenOnly>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_filter: Option<BybitOrderFilter>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub cursor: Option<String>,
}

/// Query parameters for `GET /v5/order/history`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/order-list>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitOrderHistoryParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub settle_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub open_only: Option<BybitOpenOnly>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_filter: Option<BybitOrderFilter>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_status: Option<BybitOrderStatus>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub start_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "endTime")]
    #[builder(setter(strip_option))]
    pub end_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub cursor: Option<String>,
}

/// Query parameters for `GET /v5/execution/list`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/order/execution>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitTradeHistoryParams {
    pub category: BybitProductType,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub symbol: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub base_coin: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub order_link_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub start_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "endTime")]
    #[builder(setter(strip_option))]
    pub end_time: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub exec_type: Option<BybitExecType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub cursor: Option<String>,
}

/// Request-side permission patch for API-key update endpoints (`PascalCase`).
///
/// Kept separate from the response-side
/// [`crate::http::models::BybitApiKeyPermissions`]: every field here is
/// `Option<Vec<String>>` with `skip_serializing_if`, so an unset bucket is
/// omitted from the request body entirely rather than being serialised as an
/// explicit empty array (which the venue treats as "clear all permissions").
///
/// The field set is the superset of the master and sub-account permission
/// tables. Buckets that only appear for master-account keys (`FiatP2P`,
/// `FiatBybitPay`, `FiatBitPay`, `FiatConvertBroker`, `BitCard`, `ByXPost`)
/// must not be set when calling `POST /v5/user/update-sub-api`; callers are
/// expected to only populate the buckets documented for the endpoint being
/// invoked.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey>
/// - <https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "PascalCase")]
#[builder(default)]
#[builder(setter(into, strip_option))]
pub struct BybitApiKeyPermissionUpdate {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub contract_trade: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spot: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wallet: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub derivatives: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exchange: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub earn: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub affiliate: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub block_trade: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub copy_trading: Option<Vec<String>>,
    // Bybit ships this key uppercase (`"NFT"`); the struct-level PascalCase
    // rule would otherwise emit `"Nft"` and the venue would ignore the field.
    #[serde(rename = "NFT", skip_serializing_if = "Option::is_none")]
    pub nft: Option<Vec<String>>,
    // Bybit uses `"FiatP2P"` — PascalCase rename would emit `"FiatP2p"`.
    #[serde(rename = "FiatP2P", skip_serializing_if = "Option::is_none")]
    pub fiat_p2p: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fiat_bybit_pay: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fiat_bit_pay: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fiat_convert_broker: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bit_card: Option<Vec<String>>,
    // Bybit uses `"ByXPost"` — PascalCase rename would emit `"ByxPost"`.
    #[serde(rename = "ByXPost", skip_serializing_if = "Option::is_none")]
    pub byx_post: Option<Vec<String>>,
}

/// Body parameters for `POST /v5/user/update-sub-api`.
///
/// `api_key` is only required when a master key is editing a sub-account key;
/// it is omitted when a sub key edits itself.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/user/modify-sub-apikey>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitUpdateSubApiParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub api_key: Option<String>,
    // Bybit accepts `readOnly` as a 0/1 integer on the wire; the builder takes
    // a `bool` and `opt_bool_as_int` serialises it to match.
    #[serde(skip_serializing_if = "Option::is_none", with = "opt_bool_as_int")]
    #[builder(setter(strip_option))]
    pub read_only: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub ips: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub permissions: Option<BybitApiKeyPermissionUpdate>,
}

/// Body parameters for `POST /v5/user/update-api`.
///
/// Can only modify the caller's own master key.
///
/// The official request parameter table lists only `readOnly` and
/// `permissions`; `ips` appears in the response but is not a request field,
/// so it is deliberately excluded here.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/user/modify-master-apikey>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitUpdateMasterApiParams {
    #[serde(skip_serializing_if = "Option::is_none", with = "opt_bool_as_int")]
    #[builder(setter(strip_option))]
    pub read_only: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub permissions: Option<BybitApiKeyPermissionUpdate>,
}

/// Query parameters for cursor-paginated sub-account listings.
///
/// Shared by `GET /v5/user/submembers` and `GET /v5/user/escrow_sub_members`,
/// which take the same pagination shape (`pageSize` up to 100 plus
/// `nextCursor`). Bybit documents `pageSize` as a string, but the URL encoder
/// serialises any numeric value as text anyway, so `u32` is used on the Rust
/// side for compile-time type safety.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/user/page-subuid>
/// - <https://bybit-exchange.github.io/docs/v5/user/fund-subuid-list>
#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(into))]
pub struct BybitSubMembersPageParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub page_size: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option))]
    pub next_cursor: Option<String>,
}

/// Query parameters for `GET /v5/user/sub-apikeys`.
///
/// `sub_member_id` is required, so this struct intentionally does not carry
/// `#[builder(default)]`; the generated builder returns `Err` when the field
/// is missing, matching the convention used by `BybitBorrowParams`.
///
/// # References
/// - <https://bybit-exchange.github.io/docs/v5/user/list-sub-apikeys>
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[serde(rename_all = "camelCase")]
#[builder(setter(into))]
pub struct BybitSubApiKeysParams {
    pub sub_member_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option), default)]
    pub limit: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[builder(setter(strip_option), default)]
    pub cursor: Option<String>,
}
