# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheByTimestamp
from ccxt.base.types import Any, Int, OrderBook, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError


class dydx(ccxt.async_support.dydx):

    def describe(self) -> Any:
        return self.deep_extend(super(dydx, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': False,
                'watchTicker': False,
                'watchTickers': False,
                'watchTrades': True,
                'watchOrderBook': True,
                'watchOHLCV': True,
            },
            'urls': {
                'test': {
                    'ws': 'wss://indexer.v4testnet.dydx.exchange/v4/ws',
                },
                'api': {
                    'ws': 'wss://indexer.dydx.trade/v4/ws',
                },
            },
            'options': {},
            'streaming': {},
            'exceptions': {},
        })

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://docs.dydx.xyz/indexer-client/websockets#trades

        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://github.com/ccxt/ccxt/wiki/Manual#public-trades>`
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'trade:' + market['symbol']
        request: dict = {
            'type': 'subscribe',
            'channel': 'v4_trades',
            'id': market['id'],
        }
        trades = await self.watch(url, messageHash, self.extend(request, params), messageHash)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def un_watch_trades(self, symbol: str, params={}) -> Any:
        """
        unsubscribes from the trades channel

        https://docs.dydx.xyz/indexer-client/websockets#trades

        :param str symbol: unified symbol of the market to fetch trades for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'trade:' + market['symbol']
        request: dict = {
            'type': 'unsubscribe',
            'channel': 'v4_trades',
            'id': market['id'],
        }
        return await self.watch(url, messageHash, self.extend(request, params), messageHash)

    def handle_trades(self, client, message):
        #
        # {
        #     "type": "subscribed",
        #     "connection_id": "9011edff-d8f7-47fc-bbc6-0c7b5ba7dfae",
        #     "message_id": 3,
        #     "channel": "v4_trades",
        #     "id": "BTC-USD",
        #     "contents": {
        #         "trades": [
        #             {
        #                 "id": "02b6148d0000000200000005",
        #                 "side": "BUY",
        #                 "size": "0.024",
        #                 "price": "114581",
        #                 "type": "LIMIT",
        #                 "createdAt": "2025-08-04T00:42:07.118Z",
        #                 "createdAtHeight": "45487245"
        #             }
        #         ]
        #     }
        # }
        #
        marketId = self.safe_string(message, 'id')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        content = self.safe_dict(message, 'contents')
        rawTrades = self.safe_list(content, 'trades', [])
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        parsedTrades = self.parse_trades(rawTrades, market)
        for i in range(0, len(parsedTrades)):
            parsed = parsedTrades[i]
            stored.append(parsed)
        messageHash = 'trade' + ':' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_trade(self, trade, market=None):
        #
        # {
        #     "id": "02b6148d0000000200000005",
        #     "side": "BUY",
        #     "size": "0.024",
        #     "price": "114581",
        #     "type": "LIMIT",
        #     "createdAt": "2025-08-04T00:42:07.118Z",
        #     "createdAtHeight": "45487245"
        # }
        #
        timestamp = self.parse8601(self.safe_string(trade, 'createdAt'))
        return self.safe_trade({
            'id': self.safe_string(trade, 'id'),
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': self.safe_string(market, 'symbol'),
            'order': None,
            'type': self.safe_string_lower(trade, 'type'),
            'side': self.safe_string_lower(trade, 'side'),
            'takerOrMaker': None,
            'price': self.safe_string(trade, 'price'),
            'amount': self.safe_string(trade, 'size'),
            'cost': None,
            'fee': None,
        }, market)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://docs.dydx.xyz/indexer-client/websockets#orders

        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'orderbook:' + market['symbol']
        request: dict = {
            'type': 'subscribe',
            'channel': 'v4_orderbook',
            'id': market['id'],
        }
        orderbook = await self.watch(url, messageHash, self.extend(request, params), messageHash)
        return orderbook.limit()

    async def un_watch_order_book(self, symbol: str, params={}) -> Any:
        """
        unWatches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://docs.dydx.xyz/indexer-client/websockets#orders

        :param str symbol: unified array of symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'orderbook:' + market['symbol']
        request: dict = {
            'type': 'unsubscribe',
            'channel': 'v4_orderbook',
            'id': market['id'],
        }
        return await self.watch(url, messageHash, self.extend(request, params), messageHash)

    def handle_order_book(self, client: Client, message):
        #
        # {
        #     "type": "subscribed",
        #     "connection_id": "7af140fb-b33d-4f0e-8f4c-30f16337b360",
        #     "message_id": 1,
        #     "channel": "v4_orderbook",
        #     "id": "BTC-USD",
        #     "contents": {
        #         "bids": [
        #             {
        #                 "price": "114623",
        #                 "size": "0.1112"
        #             }
        #         ],
        #         "asks": [
        #             {
        #                 "price": "114624",
        #                 "size": "0.0872"
        #             }
        #         ]
        #     }
        # }
        #
        marketId = self.safe_string(message, 'id')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        content = self.safe_dict(message, 'contents')
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            orderbook = self.order_book()
        orderbook['symbol'] = symbol
        asks = self.safe_list(content, 'asks', [])
        bids = self.safe_list(content, 'bids', [])
        self.handle_deltas(orderbook['asks'], asks)
        self.handle_deltas(orderbook['bids'], bids)
        orderbook['nonce'] = self.safe_integer(message, 'message_id')
        messageHash = 'orderbook:' + symbol
        self.orderbooks[symbol] = orderbook
        client.resolve(orderbook, messageHash)

    def handle_delta(self, bookside, delta):
        if isinstance(delta, list):
            price = self.safe_float(delta, 0)
            amount = self.safe_float(delta, 1)
            bookside.store(price, amount)
        else:
            bidAsk = self.parse_bid_ask(delta, 'price', 'size')
            bookside.storeArray(bidAsk)

    async def watch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        watches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://docs.dydx.xyz/indexer-client/websockets#candles

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'ohlcv:' + market['symbol']
        resolution = self.safe_string(self.timeframes, timeframe, timeframe)
        request: dict = {
            'type': 'subscribe',
            'channel': 'v4_candles',
            'id': market['id'] + '/' + resolution,
        }
        ohlcv = await self.watch(url, messageHash, self.extend(request, params), messageHash)
        if self.newUpdates:
            limit = ohlcv.getLimit(symbol, limit)
        return self.filter_by_since_limit(ohlcv, since, limit, 0, True)

    async def un_watch_ohlcv(self, symbol: str, timeframe='1m', params={}) -> Any:
        """
        unWatches historical candlestick data containing the open, high, low, and close price, and the volume of a market

        https://docs.dydx.xyz/indexer-client/websockets#candles

        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param dict [params.timezone]: if provided, kline intervals are interpreted in that timezone instead of UTC, example '+08:00'
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        url = self.urls['api']['ws']
        market = self.market(symbol)
        messageHash = 'ohlcv:' + market['symbol']
        resolution = self.safe_string(self.timeframes, timeframe, timeframe)
        request: dict = {
            'type': 'unsubscribe',
            'channel': 'v4_candles',
            'id': market['id'] + '/' + resolution,
        }
        return await self.watch(url, messageHash, self.extend(request, params), messageHash)

    def handle_ohlcv(self, client: Client, message):
        #
        # {
        #     "type": "subscribed",
        #     "connection_id": "e00b6e27-590c-4e91-a24d-b0645289434b",
        #     "message_id": 1,
        #     "channel": "v4_candles",
        #     "id": "BTC-USD/1MIN",
        #     "contents": {
        #         "candles": [
        #             {
        #                 "startedAt": "2025-08-05T03:40:00.000Z",
        #                 "ticker": "BTC-USD",
        #                 "resolution": "1MIN",
        #                 "low": "114249",
        #                 "high": "114256",
        #                 "open": "114256",
        #                 "close": "114249",
        #                 "baseTokenVolume": "0.4726",
        #                 "usdVolume": "53996.1818",
        #                 "trades": 7,
        #                 "startingOpenInterest": "501.7424",
        #                 "orderbookMidPriceOpen": "114255.5",
        #                 "orderbookMidPriceClose": "114255.5"
        #             }
        #         ]
        #     }
        # }
        # {
        #     "type": "channel_data",
        #     "connection_id": "e00b6e27-590c-4e91-a24d-b0645289434b",
        #     "message_id": 3,
        #     "id": "BTC-USD/1MIN",
        #     "channel": "v4_candles",
        #     "version": "1.0.0",
        #     "contents": {
        #         "startedAt": "2025-08-05T03:40:00.000Z",
        #         "ticker": "BTC-USD",
        #         "resolution": "1MIN",
        #         "low": "114249",
        #         "high": "114262",
        #         "open": "114256",
        #         "close": "114261",
        #         "baseTokenVolume": "0.4753",
        #         "usdVolume": "54304.6873",
        #         "trades": 9,
        #         "startingOpenInterest": "501.7424",
        #         "orderbookMidPriceOpen": "114255.5",
        #         "orderbookMidPriceClose": "114255.5"
        #     }
        # }
        #
        id = self.safe_string(message, 'id')
        part = id.split('/')
        interval = self.safe_string(part, 1)
        timeframe = self.find_timeframe(interval)
        marketId = self.safe_string(part, 0)
        market = self.safe_market(marketId)
        symbol = market['symbol']
        content = self.safe_dict(message, 'contents')
        candles = self.safe_list(content, 'candles')
        messageHash = 'ohlcv:' + symbol
        ohlcv = self.safe_dict(candles, 0, content)
        parsed = self.parse_ohlcv(ohlcv, market)
        self.ohlcvs[symbol] = self.safe_value(self.ohlcvs, symbol, {})
        stored = self.safe_value(self.ohlcvs[symbol], timeframe)
        if stored is None:
            limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
            stored = ArrayCacheByTimestamp(limit)
            self.ohlcvs[symbol][timeframe] = stored
        stored.append(parsed)
        client.resolve(stored, messageHash)

    def handle_error_message(self, client: Client, message):
        #
        # {
        #     "type": "error",
        #     "message": "....",
        #     "connection_id": "9011edff-d8f7-47fc-bbc6-0c7b5ba7dfae",
        #     "message_id": 4
        # }
        #
        try:
            msg = self.safe_string(message, 'message')
            raise ExchangeError(self.id + ' ' + msg)
        except Exception as e:
            client.reject(e)
        return True

    def handle_message(self, client: Client, message):
        type = self.safe_string(message, 'type')
        if type == 'error':
            self.handle_error_message(client, message)
            return
        if type is not None:
            topic = self.safe_string(message, 'channel')
            methods: dict = {
                'v4_trades': self.handle_trades,
                'v4_orderbook': self.handle_order_book,
                'v4_candles': self.handle_ohlcv,
            }
            method = self.safe_value(methods, topic)
            if method is not None:
                method(client, message)
