<?php

namespace ccxt\pro;

// 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

use Exception; // a common import
use ccxt\NotSupported;
use \React\Async;
use \React\Promise\PromiseInterface;

class upbit extends \ccxt\async\upbit {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchOrderBook' => true,
                'watchTicker' => true,
                'watchTickers' => true,
                'watchTrades' => true,
                'watchTradesForSymbols' => true,
                'watchOHLCV' => true,
                'watchOrders' => true,
                'watchMyTrades' => true,
                'watchBalance' => true,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://{hostname}/websocket/v1',
                ),
            ),
            'options' => array(
                'tradesLimit' => 1000,
            ),
        ));
    }

    public function watch_public(string $symbol, $channel, $params = array ()) {
        return Async\async(function () use ($symbol, $channel, $params) {
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $marketId = $market['id'];
            $url = $this->implode_params($this->urls['api']['ws'], array(
                'hostname' => $this->hostname,
            ));
            $this->options[$channel] = $this->safe_value($this->options, $channel, array());
            $this->options[$channel][$symbol] = true;
            $symbols = is_array($this->options[$channel]) ? array_keys($this->options[$channel]) : array();
            $marketIds = $this->market_ids($symbols);
            $request = array(
                array(
                    'ticket' => $this->uuid(),
                ),
                array(
                    'type' => $channel,
                    'codes' => $marketIds,
                    // 'isOnlySnapshot' => false,
                    // 'isOnlyRealtime' => false,
                ),
            );
            $messageHash = $channel . ':' . $marketId;
            return Async\await($this->watch($url, $messageHash, $request, $messageHash));
        }) ();
    }

    public function watch_public_multiple(?array $symbols, $channel, $params = array ()) {
        return Async\async(function () use ($symbols, $channel, $params) {
            Async\await($this->load_markets());
            if ($symbols === null) {
                $symbols = $this->symbols;
            }
            $symbols = $this->market_symbols($symbols);
            $marketIds = $this->market_ids($symbols);
            $url = $this->implode_params($this->urls['api']['ws'], array(
                'hostname' => $this->hostname,
            ));
            $messageHashes = array();
            for ($i = 0; $i < count($marketIds); $i++) {
                $messageHashes[] = $channel . ':' . $marketIds[$i];
            }
            $request = array(
                array(
                    'ticket' => $this->uuid(),
                ),
                array(
                    'type' => $channel,
                    'codes' => $marketIds,
                    // 'isOnlySnapshot' => false,
                    // 'isOnlyRealtime' => false,
                ),
            );
            return Async\await($this->watch_multiple($url, $messageHashes, $request, $messageHashes));
        }) ();
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
             *
             * @see https://global-docs.upbit.com/reference/websocket-ticker
             *
             * @param {string} $symbol unified $symbol of the market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            return Async\await($this->watch_public($symbol, 'ticker'));
        }) ();
    }

    public function watch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
             *
             * @see https://global-docs.upbit.com/reference/websocket-ticker
             *
             * @param {string[]} $symbols unified symbol of the market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            $newTickers = Async\await($this->watch_public_multiple($symbols, 'ticker'));
            if ($this->newUpdates) {
                $tickers = array();
                $tickers[$newTickers['symbol']] = $newTickers;
                return $tickers;
            }
            return $this->filter_by_array($this->tickers, 'symbol', $symbols);
        }) ();
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://global-docs.upbit.com/reference/websocket-trade
             *
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            return Async\await($this->watch_trades_for_symbols(array( $symbol ), $since, $limit, $params));
        }) ();
    }

    public function watch_trades_for_symbols(array $symbols, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $since, $limit, $params) {
            /**
             * get the list of most recent $trades for a list of $symbols
             *
             * @see https://global-docs.upbit.com/reference/websocket-trade
             *
             * @param {string[]} $symbols 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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols, null, false, true, true);
            $channel = 'trade';
            $messageHashes = array();
            $url = $this->implode_params($this->urls['api']['ws'], array(
                'hostname' => $this->hostname,
            ));
            if ($symbols !== null) {
                for ($i = 0; $i < count($symbols); $i++) {
                    $market = $this->market($symbols[$i]);
                    $marketId = $market['id'];
                    $symbol = $market['symbol'];
                    $this->options[$channel] = $this->safe_value($this->options, $channel, array());
                    $this->options[$channel][$symbol] = true;
                    $messageHashes[] = $channel . ':' . $marketId;
                }
            }
            $optionSymbols = is_array($this->options[$channel]) ? array_keys($this->options[$channel]) : array();
            $marketIds = $this->market_ids($optionSymbols);
            $request = array(
                array(
                    'ticket' => $this->uuid(),
                ),
                array(
                    'type' => $channel,
                    'codes' => $marketIds,
                    // 'isOnlySnapshot' => false,
                    // 'isOnlyRealtime' => false,
                ),
            );
            $trades = Async\await($this->watch_multiple($url, $messageHashes, $request, $messageHashes));
            if ($this->newUpdates) {
                $first = $this->safe_value($trades, 0);
                $tradeSymbol = $this->safe_string($first, 'symbol');
                $limit = $trades->getLimit ($tradeSymbol, $limit);
            }
            return $this->filter_by_since_limit($trades, $since, $limit, 'timestamp', true);
        }) ();
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://global-docs.upbit.com/reference/websocket-$orderbook
             *
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by market symbols
             */
            $orderbook = Async\await($this->watch_public($symbol, 'orderbook'));
            return $orderbook->limit ();
        }) ();
    }

    public function watch_ohlcv(string $symbol, $timeframe = '1s', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * watches information an OHLCV with timestamp, openingPrice, highPrice, lowPrice, tradePrice, baseVolume in 1s.
             *
             * @see https://docs.upbit.com/kr/reference/websocket-candle for Upbit KR
             * @see https://global-docs.upbit.com/reference/websocket-candle for Upbit Global
             *
             * @param {string} $symbol unified market $symbol of the market orders were made in
             * @param {string} $timeframe specifies the OHLCV candle interval to watch. As of now, Upbit only supports 1s candles.
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {OHLCV[]} a list of ~@link https://docs.ccxt.com/#/?id=ohlcv-structure OHLCV structures~
             */
            if ($timeframe !== '1s') {
                throw new NotSupported($this->id . ' watchOHLCV does not support' . $timeframe . ' candle.');
            }
            $timeFrameOHLCV = 'candle.' . $timeframe;
            return Async\await($this->watch_public($symbol, $timeFrameOHLCV));
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        // 2020-03-17T23:07:36.511Z "onMessage" <Buffer 7b 22 74 79 70 65 22 3a 22 74 69 63 6b 65 72 22 2c 22 63 6f 64 65 22 3a 22 42 54 43 2d 45 54 48 22 2c 22 6f 70 65 6e 69 6e 67 5f 70 72 69 63 65 22 3a ... >
        // { type => "ticker",
        //   "code" => "BTC-ETH",
        //   "opening_price" => 0.02295092,
        //   "high_price" => 0.02295092,
        //   "low_price" => 0.02161249,
        //   "trade_price" => 0.02161249,
        //   "prev_closing_price" => 0.02185802,
        //   "acc_trade_price" => 2.32732482,
        //   "change" => "FALL",
        //   "change_price" => 0.00024553,
        //   "signed_change_price" => -0.00024553,
        //   "change_rate" => 0.0112329479,
        //   "signed_change_rate" => -0.0112329479,
        //   "ask_bid" => "ASK",
        //   "trade_volume" => 2.12,
        //   "acc_trade_volume" => 106.11798418,
        //   "trade_date" => "20200317",
        //   "trade_time" => "215843",
        //   "trade_timestamp" => 1584482323000,
        //   "acc_ask_volume" => 90.16935908,
        //   "acc_bid_volume" => 15.9486251,
        //   "highest_52_week_price" => 0.03537414,
        //   "highest_52_week_date" => "2019-04-08",
        //   "lowest_52_week_price" => 0.01614901,
        //   "lowest_52_week_date" => "2019-09-06",
        //   "trade_status" => null,
        //   "market_state" => "ACTIVE",
        //   "market_state_for_ios" => null,
        //   "is_trading_suspended" => false,
        //   "delisting_date" => null,
        //   "market_warning" => "NONE",
        //   "timestamp" => 1584482323378,
        //   "acc_trade_price_24h" => 2.5955306323568927,
        //   "acc_trade_volume_24h" => 118.38798416,
        //   "stream_type" => "SNAPSHOT" }
        $marketId = $this->safe_string($message, 'code');
        $messageHash = 'ticker:' . $marketId;
        $ticker = $this->parse_ticker($message);
        $symbol = $ticker['symbol'];
        $this->tickers[$symbol] = $ticker;
        $client->resolve ($ticker, $messageHash);
    }

    public function handle_order_book(Client $client, $message) {
        // { $type => "orderbook",
        //   "code" => "BTC-ETH",
        //   "timestamp" => 1584486737444,
        //   "total_ask_size" => 16.76384456,
        //   "total_bid_size" => 168.9020623,
        //   "orderbook_units":
        //    array( array( $ask_price => 0.02295077,
        //        "bid_price" => 0.02161249,
        //        "ask_size" => 3.57100696,
        //        "bid_size" => 22.5303265 ),
        //      array( $ask_price => 0.02295078,
        //        "bid_price" => 0.02152658,
        //        "ask_size" => 0.52451651,
        //        "bid_size" => 2.30355128 ),
        //      array( $ask_price => 0.02295086,
        //        "bid_price" => 0.02150802,
        //        "ask_size" => 1.585,
        //        "bid_size" => 5 ), ... ),
        //   "stream_type" => "SNAPSHOT" }
        $marketId = $this->safe_string($message, 'code');
        $symbol = $this->safe_symbol($marketId, null, '-');
        $type = $this->safe_string($message, 'stream_type');
        $options = $this->safe_value($this->options, 'watchOrderBook', array());
        $limit = $this->safe_integer($options, 'limit', 15);
        if ($type === 'SNAPSHOT') {
            $this->orderbooks[$symbol] = $this->order_book(array(), $limit);
        }
        $orderbook = $this->orderbooks[$symbol];
        // upbit always returns a snapshot of 15 topmost entries
        // the "REALTIME" deltas are not incremental
        // therefore we reset the $orderbook on each update
        // and reinitialize it again with new bidasks
        $orderbook->reset (array());
        $orderbook['symbol'] = $symbol;
        $bids = $orderbook['bids'];
        $asks = $orderbook['asks'];
        $data = $this->safe_value($message, 'orderbook_units', array());
        for ($i = 0; $i < count($data); $i++) {
            $entry = $data[$i];
            $ask_price = $this->safe_float($entry, 'ask_price');
            $ask_size = $this->safe_float($entry, 'ask_size');
            $bid_price = $this->safe_float($entry, 'bid_price');
            $bid_size = $this->safe_float($entry, 'bid_size');
            $asks->store ($ask_price, $ask_size);
            $bids->store ($bid_price, $bid_size);
        }
        $timestamp = $this->safe_integer($message, 'timestamp');
        $datetime = $this->iso8601($timestamp);
        $orderbook['timestamp'] = $timestamp;
        $orderbook['datetime'] = $datetime;
        $messageHash = 'orderbook:' . $marketId;
        $client->resolve ($orderbook, $messageHash);
    }

    public function handle_trades(Client $client, $message) {
        // { type => "trade",
        //   "code" => "KRW-BTC",
        //   "timestamp" => 1584508285812,
        //   "trade_date" => "2020-03-18",
        //   "trade_time" => "05:11:25",
        //   "trade_timestamp" => 1584508285000,
        //   "trade_price" => 6747000,
        //   "trade_volume" => 0.06499468,
        //   "ask_bid" => "ASK",
        //   "prev_closing_price" => 6774000,
        //   "change" => "FALL",
        //   "change_price" => 27000,
        //   "sequential_id" => 1584508285000002,
        //   "stream_type" => "REALTIME" }
        $trade = $this->parse_trade($message);
        $symbol = $trade['symbol'];
        $stored = $this->safe_value($this->trades, $symbol);
        if ($stored === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $stored = new ArrayCache ($limit);
            $this->trades[$symbol] = $stored;
        }
        $stored->append ($trade);
        $marketId = $this->safe_string($message, 'code');
        $messageHash = 'trade:' . $marketId;
        $client->resolve ($stored, $messageHash);
    }

    public function handle_ohlcv(Client $client, $message) {
        // {
        //     type => 'candle.1s',
        //     code => 'KRW-USDT',
        //     candle_date_time_utc => '2025-04-22T09:50:34',
        //     candle_date_time_kst => '2025-04-22T18:50:34',
        //     opening_price => 1438,
        //     high_price => 1438,
        //     low_price => 1438,
        //     trade_price => 1438,
        //     candle_acc_trade_volume => 1145.8935,
        //     candle_acc_trade_price => 1647794.853,
        //     timestamp => 1745315434125,
        //     stream_type => 'REALTIME'
        //   }
        $marketId = $this->safe_string($message, 'code');
        $messageHash = 'candle.1s:' . $marketId;
        $ohlcv = $this->parse_ohlcv($message);
        $client->resolve ($ohlcv, $messageHash);
    }

    public function authenticate($params = array ()) {
        $this->check_required_credentials();
        $wsOptions = $this->safe_dict($this->options, 'ws', array());
        $authenticated = $this->safe_string($wsOptions, 'token');
        if ($authenticated === null) {
            $auth = array(
                'access_key' => $this->apiKey,
                'nonce' => $this->uuid(),
            );
            $token = $this->jwt($auth, $this->encode($this->secret), 'sha256', false);
            $wsOptions['token'] = $token;
            $wsOptions['options'] = array(
                'headers' => array(
                    'authorization' => 'Bearer ' . $token,
                ),
            );
            $this->options['ws'] = $wsOptions;
        }
        $url = $this->urls['api']['ws'] . '/private';
        $client = $this->client($url);
        return $client;
    }

    public function watch_private($symbol, $channel, $messageHash, $params = array ()) {
        return Async\async(function () use ($symbol, $channel, $messageHash, $params) {
            Async\await($this->authenticate());
            $request = array(
                'type' => $channel,
            );
            if ($symbol !== null) {
                Async\await($this->load_markets());
                $market = $this->market($symbol);
                $symbol = $market['symbol'];
                $symbols = array( $symbol );
                $marketIds = $this->market_ids($symbols);
                $request['codes'] = $marketIds;
                $messageHash = $messageHash . ':' . $symbol;
            }
            $url = $this->implode_params($this->urls['api']['ws'], array(
                'hostname' => $this->hostname,
            ));
            $url .= '/private';
            $message = array(
                array(
                    'ticket' => $this->uuid(),
                ),
                $request,
            );
            return Async\await($this->watch($url, $messageHash, $message, $messageHash));
        }) ();
    }

    public function watch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * watches information on multiple $orders made by the user
             *
             * @see https://global-docs.upbit.com/reference/websocket-myorder
             *
             * @param {string} $symbol unified market $symbol of the market $orders were made in
             * @param {int} [$since] the earliest time in ms to fetch $orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $channel = 'myOrder';
            $messageHash = 'myOrder';
            $orders = Async\await($this->watch_private($symbol, $channel, $messageHash));
            if ($this->newUpdates) {
                $limit = $orders->getLimit ($symbol, $limit);
            }
            return $this->filter_by_symbol_since_limit($orders, $symbol, $since, $limit, true);
        }) ();
    }

    public function watch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * watches information on multiple $trades made by the user
             *
             * @see https://global-docs.upbit.com/reference/websocket-myorder
             *
             * @param {string} $symbol unified market $symbol of the market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $channel = 'myOrder';
            $messageHash = 'myTrades';
            $trades = Async\await($this->watch_private($symbol, $channel, $messageHash));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($symbol, $limit);
            }
            return $this->filter_by_symbol_since_limit($trades, $symbol, $since, $limit, true);
        }) ();
    }

    public function parse_ws_order_status(?string $status) {
        $statuses = array(
            'wait' => 'open',
            'done' => 'closed',
            'cancel' => 'canceled',
            'watch' => 'open', // not sure what this $status means
            'trade' => 'open',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_ws_order($order, $market = null) {
        //
        // {
        //     "type" => "myOrder",
        //     "code" => "SGD-XRP",
        //     "uuid" => "ac2dc2a3-fce9-40a2-a4f6-5987c25c438f",
        //     "ask_bid" => "BID",
        //     "order_type" => "limit",
        //     "state" => "trade",
        //     "price" => 0.001453,
        //     "avg_price" => 0.00145372,
        //     "volume" => 30925891.29839369,
        //     "remaining_volume" => 29968038.09235948,
        //     "executed_volume" => 30925891.29839369,
        //     "trades_count" => 1,
        //     "reserved_fee" => 44.23943970238218,
        //     "remaining_fee" => 21.77177967409916,
        //     "paid_fee" => 22.467660028283017,
        //     "locked" => 43565.33112787242,
        //     "executed_funds" => 44935.32005656603,
        //     "order_timestamp" => 1710751590000,
        //     "timestamp" => 1710751597500,
        //     "stream_type" => "REALTIME"
        // }
        //
        $id = $this->safe_string($order, 'uuid');
        $side = $this->safe_string_lower($order, 'ask_bid');
        if ($side === 'bid') {
            $side = 'buy';
        } else {
            $side = 'sell';
        }
        $timestamp = $this->parse8601($this->safe_string($order, 'order_timestamp'));
        $status = $this->parse_ws_order_status($this->safe_string($order, 'state'));
        $marketId = $this->safe_string($order, 'code');
        $market = $this->safe_market($marketId, $market);
        $fee = null;
        $feeCost = $this->safe_string($order, 'paid_fee');
        if ($feeCost !== null) {
            $fee = array(
                'currency' => $market['quote'],
                'cost' => $feeCost,
            );
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => null,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $this->safe_string($order, 'trade_timestamp'),
            'symbol' => $market['symbol'],
            'type' => $this->safe_string($order, 'order_type'),
            'timeInForce' => $this->safe_string($order, 'time_in_force'),
            'postOnly' => null,
            'side' => $side,
            'price' => $this->safe_string($order, 'price'),
            'stopPrice' => null,
            'triggerPrice' => null,
            'cost' => $this->safe_string($order, 'executed_funds'),
            'average' => $this->safe_string($order, 'avg_price'),
            'amount' => $this->safe_string($order, 'volume'),
            'filled' => $this->safe_string($order, 'executed_volume'),
            'remaining' => $this->safe_string($order, 'remaining_volume'),
            'status' => $status,
            'fee' => $fee,
            'trades' => null,
        ));
    }

    public function parse_ws_trade($trade, $market = null) {
        // see => parseWsOrder
        $side = $this->safe_string_lower($trade, 'ask_bid');
        if ($side === 'bid') {
            $side = 'buy';
        } else {
            $side = 'sell';
        }
        $timestamp = $this->parse8601($this->safe_string($trade, 'trade_timestamp'));
        $marketId = $this->safe_string($trade, 'code');
        $market = $this->safe_market($marketId, $market);
        $fee = null;
        $feeCost = $this->safe_string($trade, 'paid_fee');
        if ($feeCost !== null) {
            $fee = array(
                'currency' => $market['quote'],
                'cost' => $feeCost,
            );
        }
        return $this->safe_trade(array(
            'id' => $this->safe_string($trade, 'trade_uuid'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'side' => $side,
            'price' => $this->safe_string($trade, 'price'),
            'amount' => $this->safe_string($trade, 'volume'),
            'cost' => $this->safe_string($trade, 'executed_funds'),
            'order' => $this->safe_string($trade, 'uuid'),
            'takerOrMaker' => null,
            'type' => $this->safe_string($trade, 'order_type'),
            'fee' => $fee,
            'info' => $trade,
        ), $market);
    }

    public function handle_my_order(Client $client, $message) {
        // see => parseWsOrder
        $tradeId = $this->safe_string($message, 'trade_uuid');
        if ($tradeId !== null) {
            $this->handle_my_trade($client, $message);
        }
        $this->handle_order($client, $message);
    }

    public function handle_my_trade(Client $client, $message) {
        // see => parseWsOrder
        $myTrades = $this->myTrades;
        if ($myTrades === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $myTrades = new ArrayCacheBySymbolById ($limit);
        }
        $trade = $this->parse_ws_trade($message);
        $myTrades->append ($trade);
        $messageHash = 'myTrades';
        $client->resolve ($myTrades, $messageHash);
        $messageHash = 'myTrades:' . $trade['symbol'];
        $client->resolve ($myTrades, $messageHash);
    }

    public function handle_order(Client $client, $message) {
        $parsed = $this->parse_ws_order($message);
        $symbol = $this->safe_string($parsed, 'symbol');
        $orderId = $this->safe_string($parsed, 'id');
        if ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $this->orders = new ArrayCacheBySymbolById ($limit);
        }
        $cachedOrders = $this->orders;
        $orders = $this->safe_value($cachedOrders->hashmap, $symbol, array());
        $order = $this->safe_value($orders, $orderId);
        if ($order !== null) {
            $fee = $this->safe_value($order, 'fee');
            if ($fee !== null) {
                $parsed['fee'] = $fee;
            }
            $fees = $this->safe_value($order, 'fees');
            if ($fees !== null) {
                $parsed['fees'] = $fees;
            }
            $parsed['trades'] = $this->safe_value($order, 'trades');
            $parsed['timestamp'] = $this->safe_integer($order, 'timestamp');
            $parsed['datetime'] = $this->safe_string($order, 'datetime');
        }
        $cachedOrders->append ($parsed);
        $messageHash = 'myOrder';
        $client->resolve ($this->orders, $messageHash);
        $messageHash = $messageHash . ':' . $symbol;
        $client->resolve ($this->orders, $messageHash);
    }

    public function watch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             *
             * @see https://global-docs.upbit.com/reference/websocket-myasset
             *
             * query for balance and get the amount of funds available for trading or funds locked in orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $channel = 'myAsset';
            $messageHash = 'myAsset';
            return Async\await($this->watch_private(null, $channel, $messageHash));
        }) ();
    }

    public function handle_balance(Client $client, $message) {
        //
        // {
        //     "type" => "myAsset",
        //     "asset_uuid" => "e635f223-1609-4969-8fb6-4376937baad6",
        //     "assets" => array(
        //       {
        //         "currency" => "SGD",
        //         "balance" => 1386929.37231066771348207123,
        //         "locked" => 10329.670127489597585685
        //       }
        //     ),
        //     "asset_timestamp" => 1710146517259,
        //     "timestamp" => 1710146517267,
        //     "stream_type" => "REALTIME"
        // }
        //
        $data = $this->safe_list($message, 'assets', array());
        $timestamp = $this->safe_integer($message, 'timestamp');
        $this->balance['timestamp'] = $timestamp;
        $this->balance['datetime'] = $this->iso8601($timestamp);
        for ($i = 0; $i < count($data); $i++) {
            $balance = $data[$i];
            $currencyId = $this->safe_string($balance, 'currency');
            $code = $this->safe_currency_code($currencyId);
            $available = $this->safe_string($balance, 'balance');
            $frozen = $this->safe_string($balance, 'locked');
            $account = $this->account();
            $account['free'] = $available;
            $account['used'] = $frozen;
            $this->balance[$code] = $account;
            $this->balance = $this->safe_balance($this->balance);
        }
        $messageHash = $this->safe_string($message, 'type');
        $client->resolve ($this->balance, $messageHash);
    }

    public function handle_message(Client $client, $message) {
        $methods = array(
            'ticker' => array($this, 'handle_ticker'),
            'orderbook' => array($this, 'handle_order_book'),
            'trade' => array($this, 'handle_trades'),
            'myOrder' => array($this, 'handle_my_order'),
            'myAsset' => array($this, 'handle_balance'),
            'candle.1s' => array($this, 'handle_ohlcv'),
        );
        $methodName = $this->safe_string($message, 'type');
        $method = $this->safe_value($methods, $methodName);
        if ($method) {
            $method($client, $message);
        }
    }
}
