// Copyright 2023 The Pigweed Authors
//
// Licensed 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
//
//     https://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.

#include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h"

#include <cpp-string/string_printf.h>
#include <endian.h>
#include <lib/fit/defer.h>
#include <pw_bluetooth/hci_android.emb.h>
#include <pw_bluetooth/hci_common.emb.h>

#include "pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "pw_bluetooth_sapphire/internal/host/common/log.h"
#include "pw_bluetooth_sapphire/internal/host/common/trace.h"
#include "pw_bluetooth_sapphire/internal/host/transport/slab_allocators.h"

namespace bt::hci {

namespace {

// Helper for std::variant
template <class... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};
// explicit deduction guide (not needed in C++20)
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

}  // namespace

static bool IsAsync(hci_spec::EventCode code) {
  return code != hci_spec::kCommandCompleteEventCode &&
         code != hci_spec::kCommandStatusEventCode;
}

static std::string EventTypeToString(CommandChannel::EventType event_type) {
  switch (event_type) {
    case CommandChannel::EventType::kHciEvent:
      return "hci_event";
    case CommandChannel::EventType::kLEMetaEvent:
      return "le_meta_event";
    case CommandChannel::EventType::kVendorEvent:
      return "vendor_event";
  }
}

CommandChannel::QueuedCommand::QueuedCommand(
    CommandPacketVariant command_packet,
    std::unique_ptr<TransactionData> transaction_data)
    : packet(std::move(command_packet)), data(std::move(transaction_data)) {
  BT_DEBUG_ASSERT(data);
}

CommandChannel::TransactionData::TransactionData(
    CommandChannel* channel,
    TransactionId transaction_id,
    hci_spec::OpCode opcode,
    hci_spec::EventCode complete_event_code,
    std::optional<hci_spec::EventCode> le_meta_subevent_code,
    std::unordered_set<hci_spec::OpCode> exclusions,
    CommandCallbackVariant callback)
    : channel_(channel),
      transaction_id_(transaction_id),
      opcode_(opcode),
      complete_event_code_(complete_event_code),
      le_meta_subevent_code_(le_meta_subevent_code),
      exclusions_(std::move(exclusions)),
      callback_(std::move(callback)),
      timeout_task_(channel_->dispatcher_),
      handler_id_(0u) {
  BT_DEBUG_ASSERT(transaction_id != 0u);
  exclusions_.insert(opcode_);
}

CommandChannel::TransactionData::~TransactionData() {
  std::visit(
      [this](auto& cb) {
        if (cb) {
          bt_log(DEBUG,
                 "hci",
                 "destroying unfinished transaction: %zu",
                 transaction_id_);
        }
      },
      callback_);
}

void CommandChannel::TransactionData::StartTimer() {
  // Transactions should only ever be started once.
  BT_DEBUG_ASSERT(!timeout_task_.is_pending());
  timeout_task_.set_function(
      [chan = channel_, tid = id()](auto, pw::Status status) {
        if (status.ok()) {
          chan->OnCommandTimeout(tid);
        }
      });
  timeout_task_.PostAfter(hci_spec::kCommandTimeout);
}

void CommandChannel::TransactionData::Complete(
    std::unique_ptr<EventPacket> event) {
  timeout_task_.Cancel();

  std::visit(
      [this, &event](auto& cb) {
        using T = std::decay_t<decltype(cb)>;

        if (!cb) {
          return;
        }

        // Call callback_ synchronously to ensure that asynchronous status &
        // complete events are not handled out of order if they are dispatched
        // from the HCI API simultaneously.
        if constexpr (std::is_same_v<T, CommandCallback>) {
          cb(transaction_id_, *event);
        } else {
          EmbossEventPacket packet =
              EmbossEventPacket::New(event->view().size());
          MutableBufferView view = packet.mutable_data();
          event->view().data().Copy(&view);
          cb(transaction_id_, packet);
        }

        // Asynchronous commands will have an additional reference to callback_
        // in the event map. Clear this reference to ensure that destruction or
        // unexpected command complete events or status events do not call this
        // reference to callback_ twice.
        cb = nullptr;
      },
      callback_);
}

void CommandChannel::TransactionData::Cancel() {
  timeout_task_.Cancel();
  std::visit([](auto& cb) { cb = nullptr; }, callback_);
}

CommandChannel::EventCallbackVariant
CommandChannel::TransactionData::MakeCallback() {
  return std::visit(
      overloaded{[this](CommandCallback& cb) -> EventCallbackVariant {
                   return [transaction_id = transaction_id_,
                           cb = cb.share()](const EventPacket& event) {
                     cb(transaction_id, event);
                     return EventCallbackResult::kContinue;
                   };
                 },
                 [this](EmbossCommandCallback& cb) -> EventCallbackVariant {
                   return [transaction_id = transaction_id_,
                           cb = cb.share()](const EmbossEventPacket& event) {
                     cb(transaction_id, event);
                     return EventCallbackResult::kContinue;
                   };
                 }},
      callback_);
}

CommandChannel::CommandChannel(pw::bluetooth::Controller* hci,
                               pw::async::Dispatcher& dispatcher)
    : next_transaction_id_(1u),
      next_event_handler_id_(1u),
      hci_(hci),
      allowed_command_packets_(1u),
      dispatcher_(dispatcher),
      weak_ptr_factory_(this) {
  hci_->SetEventFunction(fit::bind_member<&CommandChannel::OnEvent>(this));

  bt_log(DEBUG, "hci", "CommandChannel initialized");
}

CommandChannel::~CommandChannel() {
  bt_log(INFO, "hci", "CommandChannel destroyed");
  hci_->SetEventFunction(nullptr);
}

CommandChannel::TransactionId CommandChannel::SendCommand(
    CommandPacketVariant command_packet,
    CommandCallback callback,
    const hci_spec::EventCode complete_event_code) {
  return SendExclusiveCommand(
      std::move(command_packet), std::move(callback), complete_event_code);
}

CommandChannel::TransactionId CommandChannel::SendLeAsyncCommand(
    CommandPacketVariant command_packet,
    CommandCallback callback,
    hci_spec::EventCode le_meta_subevent_code) {
  return SendLeAsyncExclusiveCommand(
      std::move(command_packet), std::move(callback), le_meta_subevent_code);
}

CommandChannel::TransactionId CommandChannel::SendExclusiveCommand(
    CommandPacketVariant command_packet,
    CommandCallbackVariant callback,
    const hci_spec::EventCode complete_event_code,
    std::unordered_set<hci_spec::OpCode> exclusions) {
  return SendExclusiveCommandInternal(std::move(command_packet),
                                      std::move(callback),
                                      complete_event_code,
                                      std::nullopt,
                                      std::move(exclusions));
}

CommandChannel::TransactionId CommandChannel::SendLeAsyncExclusiveCommand(
    CommandPacketVariant command_packet,
    CommandCallback callback,
    std::optional<hci_spec::EventCode> le_meta_subevent_code,
    std::unordered_set<hci_spec::OpCode> exclusions) {
  return SendExclusiveCommandInternal(std::move(command_packet),
                                      std::move(callback),
                                      hci_spec::kLEMetaEventCode,
                                      le_meta_subevent_code,
                                      std::move(exclusions));
}

CommandChannel::TransactionId CommandChannel::SendExclusiveCommandInternal(
    CommandPacketVariant command_packet,
    CommandCallbackVariant callback,
    hci_spec::EventCode complete_event_code,
    std::optional<hci_spec::EventCode> le_meta_subevent_code,
    std::unordered_set<hci_spec::OpCode> exclusions) {
  if (!active_) {
    bt_log(INFO, "hci", "ignoring command (CommandChannel is inactive)");
    return 0;
  }

  BT_ASSERT_MSG((complete_event_code == hci_spec::kLEMetaEventCode) ==
                    le_meta_subevent_code.has_value(),
                "only LE Meta Event subevents are supported");

  if (IsAsync(complete_event_code)) {
    // Cannot send an asynchronous command if there's an external event handler
    // registered for the completion event.
    EventHandlerData* handler = nullptr;
    if (le_meta_subevent_code.has_value()) {
      handler = FindLEMetaEventHandler(*le_meta_subevent_code);
    } else {
      handler = FindEventHandler(complete_event_code);
    }

    if (handler && !handler->is_async()) {
      bt_log(DEBUG, "hci", "event handler already handling this event");
      return 0u;
    }
  }

  if (next_transaction_id_.value() == 0u) {
    next_transaction_id_.Set(1);
  }

  const hci_spec::OpCode opcode = std::visit(
      overloaded{[](std::unique_ptr<CommandPacket>& p) { return p->opcode(); },
                 [](EmbossCommandPacket& p) { return p.opcode(); }},
      command_packet);
  const TransactionId transaction_id = next_transaction_id_.value();
  next_transaction_id_.Set(transaction_id + 1);

  std::unique_ptr<CommandChannel::TransactionData> data =
      std::make_unique<TransactionData>(this,
                                        transaction_id,
                                        opcode,
                                        complete_event_code,
                                        le_meta_subevent_code,
                                        std::move(exclusions),
                                        std::move(callback));

  QueuedCommand command(std::move(command_packet), std::move(data));

  if (IsAsync(complete_event_code)) {
    MaybeAddTransactionHandler(command.data.get());
  }

  send_queue_.push_back(std::move(command));
  TrySendQueuedCommands();

  return transaction_id;
}

bool CommandChannel::RemoveQueuedCommand(TransactionId transaction_id) {
  auto it = std::find_if(send_queue_.begin(),
                         send_queue_.end(),
                         [transaction_id](const QueuedCommand& cmd) {
                           return cmd.data->id() == transaction_id;
                         });
  if (it == send_queue_.end()) {
    // The transaction to remove has already finished or never existed.
    bt_log(
        TRACE, "hci", "command to remove not found, id: %zu", transaction_id);
    return false;
  }

  bt_log(TRACE, "hci", "removing queued command id: %zu", transaction_id);
  TransactionData& data = *it->data;
  data.Cancel();

  RemoveEventHandlerInternal(data.handler_id());
  send_queue_.erase(it);
  return true;
}

CommandChannel::EventHandlerId CommandChannel::AddEventHandler(
    hci_spec::EventCode event_code,
    EventCallbackVariant event_callback_variant) {
  if (event_code == hci_spec::kCommandStatusEventCode ||
      event_code == hci_spec::kCommandCompleteEventCode ||
      event_code == hci_spec::kLEMetaEventCode) {
    return 0u;
  }

  EventHandlerData* handler = FindEventHandler(event_code);
  if (handler && handler->is_async()) {
    bt_log(ERROR,
           "hci",
           "async event handler %zu already registered for event code %#.2x",
           handler->handler_id,
           event_code);
    return 0u;
  }

  EventHandlerId handler_id =
      NewEventHandler(event_code,
                      EventType::kHciEvent,
                      hci_spec::kNoOp,
                      std::move(event_callback_variant));
  event_code_handlers_.emplace(event_code, handler_id);
  return handler_id;
}

CommandChannel::EventHandlerId CommandChannel::AddLEMetaEventHandler(
    hci_spec::EventCode le_meta_subevent_code,
    EventCallbackVariant event_callback) {
  EventHandlerData* handler = FindLEMetaEventHandler(le_meta_subevent_code);
  if (handler && handler->is_async()) {
    bt_log(ERROR,
           "hci",
           "async event handler %zu already registered for LE Meta Event "
           "subevent code %#.2x",
           handler->handler_id,
           le_meta_subevent_code);
    return 0u;
  }

  EventHandlerId handler_id = NewEventHandler(le_meta_subevent_code,
                                              EventType::kLEMetaEvent,
                                              hci_spec::kNoOp,
                                              std::move(event_callback));
  le_meta_subevent_code_handlers_.emplace(le_meta_subevent_code, handler_id);
  return handler_id;
}

CommandChannel::EventHandlerId CommandChannel::AddVendorEventHandler(
    hci_spec::EventCode vendor_subevent_code,
    EventCallbackVariant event_callback) {
  CommandChannel::EventHandlerData* handler =
      FindVendorEventHandler(vendor_subevent_code);
  if (handler && handler->is_async()) {
    bt_log(ERROR,
           "hci",
           "async event handler %zu already registered for Vendor Event "
           "subevent code %#.2x",
           handler->handler_id,
           vendor_subevent_code);
    return 0u;
  }

  EventHandlerId handler_id = NewEventHandler(vendor_subevent_code,
                                              EventType::kVendorEvent,
                                              hci_spec::kNoOp,
                                              std::move(event_callback));
  vendor_subevent_code_handlers_.emplace(vendor_subevent_code, handler_id);
  return handler_id;
}

void CommandChannel::RemoveEventHandler(EventHandlerId handler_id) {
  // If the ID doesn't exist or it is internal. it can't be removed.
  auto iter = event_handler_id_map_.find(handler_id);
  if (iter == event_handler_id_map_.end() || iter->second.is_async()) {
    return;
  }

  RemoveEventHandlerInternal(handler_id);
}

CommandChannel::EventHandlerData* CommandChannel::FindEventHandler(
    hci_spec::EventCode code) {
  auto it = event_code_handlers_.find(code);
  if (it == event_code_handlers_.end()) {
    return nullptr;
  }
  return &event_handler_id_map_[it->second];
}

CommandChannel::EventHandlerData* CommandChannel::FindLEMetaEventHandler(
    hci_spec::EventCode le_meta_subevent_code) {
  auto it = le_meta_subevent_code_handlers_.find(le_meta_subevent_code);
  if (it == le_meta_subevent_code_handlers_.end()) {
    return nullptr;
  }
  return &event_handler_id_map_[it->second];
}

CommandChannel::EventHandlerData* CommandChannel::FindVendorEventHandler(
    hci_spec::EventCode vendor_subevent_code) {
  auto it = vendor_subevent_code_handlers_.find(vendor_subevent_code);
  if (it == vendor_subevent_code_handlers_.end()) {
    return nullptr;
  }

  return &event_handler_id_map_[it->second];
}

void CommandChannel::RemoveEventHandlerInternal(EventHandlerId handler_id) {
  auto iter = event_handler_id_map_.find(handler_id);
  if (iter == event_handler_id_map_.end()) {
    return;
  }

  std::unordered_multimap<hci_spec::EventCode, EventHandlerId>* handlers =
      nullptr;
  switch (iter->second.event_type) {
    case EventType::kHciEvent:
      handlers = &event_code_handlers_;
      break;
    case EventType::kLEMetaEvent:
      handlers = &le_meta_subevent_code_handlers_;
      break;
    case EventType::kVendorEvent:
      handlers = &vendor_subevent_code_handlers_;
      break;
  }

  bt_log(TRACE,
         "hci",
         "removing handler for %s event code %#.2x",
         EventTypeToString(iter->second.event_type).c_str(),
         iter->second.event_code);

  auto range = handlers->equal_range(iter->second.event_code);
  for (auto it = range.first; it != range.second; ++it) {
    if (it->second == handler_id) {
      it = handlers->erase(it);
      break;
    }
  }

  event_handler_id_map_.erase(iter);
}

void CommandChannel::TrySendQueuedCommands() {
  if (allowed_command_packets_.value() == 0) {
    bt_log(TRACE, "hci", "controller queue full, waiting");
    return;
  }

  // Walk the waiting and see if any are sendable.
  for (auto it = send_queue_.begin();
       allowed_command_packets_.value() > 0 && it != send_queue_.end();) {
    // Care must be taken not to dangle this reference if its owner
    // QueuedCommand is destroyed.
    const TransactionData& data = *it->data;

    // Can't send if another is running with an opcode this can't coexist with.
    bool excluded = false;
    for (hci_spec::OpCode excluded_opcode : data.exclusions()) {
      if (pending_transactions_.count(excluded_opcode) != 0) {
        bt_log(TRACE,
               "hci",
               "pending command (%#.4x) delayed due to running opcode %#.4x",
               it->data->opcode(),
               excluded_opcode);
        excluded = true;
        break;
      }
    }
    if (excluded) {
      ++it;
      continue;
    }

    bool transaction_waiting_on_event =
        event_code_handlers_.count(data.complete_event_code());
    bool transaction_waiting_on_subevent =
        data.le_meta_subevent_code() &&
        le_meta_subevent_code_handlers_.count(*data.le_meta_subevent_code());
    bool waiting_for_other_transaction =
        transaction_waiting_on_event || transaction_waiting_on_subevent;

    // We can send this if we only expect one update, or if we aren't waiting
    // for another transaction to complete on the same event. It is unlikely but
    // possible to have commands with different opcodes wait on the same
    // completion event.
    if (!IsAsync(data.complete_event_code()) || data.handler_id() != 0 ||
        !waiting_for_other_transaction) {
      bt_log(
          TRACE, "hci", "sending previously queued command id %zu", data.id());
      SendQueuedCommand(std::move(*it));
      it = send_queue_.erase(it);
      continue;
    }
    ++it;
  }
}

void CommandChannel::SendQueuedCommand(QueuedCommand&& cmd) {
  pw::span packet_span = std::visit(
      overloaded{[](std::unique_ptr<CommandPacket>& p) {
                   return p->view().data().subspan();
                 },
                 [](EmbossCommandPacket& p) { return p.data().subspan(); }},
      cmd.packet);
  hci_->SendCommand(packet_span);

  allowed_command_packets_.Set(allowed_command_packets_.value() - 1);

  std::unique_ptr<TransactionData>& transaction = cmd.data;

  transaction->StartTimer();

  MaybeAddTransactionHandler(transaction.get());

  pending_transactions_.insert(
      std::make_pair(transaction->opcode(), std::move(transaction)));
}

void CommandChannel::MaybeAddTransactionHandler(TransactionData* data) {
  // We don't need to add a transaction handler for synchronous transactions.
  if (!IsAsync(data->complete_event_code())) {
    return;
  }

  EventType event_type = EventType::kHciEvent;
  std::unordered_multimap<hci_spec::EventCode, EventHandlerId>* handlers =
      nullptr;

  if (data->le_meta_subevent_code().has_value()) {
    event_type = EventType::kLEMetaEvent;
    handlers = &le_meta_subevent_code_handlers_;
  } else {
    event_type = EventType::kHciEvent;
    handlers = &event_code_handlers_;
  }

  const hci_spec::EventCode code =
      data->le_meta_subevent_code().value_or(data->complete_event_code());

  // We already have a handler for this transaction, or another transaction is
  // already waiting and it will be queued.
  if (handlers->count(code)) {
    bt_log(TRACE, "hci", "async command %zu: already has handler", data->id());
    return;
  }

  EventHandlerId handler_id =
      NewEventHandler(code, event_type, data->opcode(), data->MakeCallback());

  BT_ASSERT(handler_id != 0u);
  data->set_handler_id(handler_id);
  handlers->emplace(code, handler_id);
  bt_log(TRACE,
         "hci",
         "async command %zu assigned handler %zu",
         data->id(),
         handler_id);
}

CommandChannel::EventHandlerId CommandChannel::NewEventHandler(
    hci_spec::EventCode event_code,
    EventType event_type,
    hci_spec::OpCode pending_opcode,
    EventCallbackVariant event_callback_variant) {
  BT_DEBUG_ASSERT(event_code);
  BT_DEBUG_ASSERT(
      (std::holds_alternative<EventCallback>(event_callback_variant) &&
       std::get<EventCallback>(event_callback_variant)) ||
      (std::holds_alternative<EmbossEventCallback>(event_callback_variant) &&
       std::get<EmbossEventCallback>(event_callback_variant)));

  auto handler_id = next_event_handler_id_.value();
  next_event_handler_id_.Set(handler_id + 1);
  EventHandlerData data;
  data.handler_id = handler_id;
  data.event_code = event_code;
  data.event_type = event_type;
  data.pending_opcode = pending_opcode;
  data.event_callback = std::move(event_callback_variant);

  bt_log(TRACE,
         "hci",
         "adding event handler %zu for %s event code %#.2x",
         handler_id,
         EventTypeToString(event_type).c_str(),
         event_code);
  BT_DEBUG_ASSERT(event_handler_id_map_.find(handler_id) ==
                  event_handler_id_map_.end());
  event_handler_id_map_[handler_id] = std::move(data);

  return handler_id;
}

void CommandChannel::UpdateTransaction(std::unique_ptr<EventPacket> event) {
  hci_spec::EventCode event_code = event->event_code();

  BT_DEBUG_ASSERT(event_code == hci_spec::kCommandStatusEventCode ||
                  event_code == hci_spec::kCommandCompleteEventCode);

  hci_spec::OpCode matching_opcode;

  // The HCI Command Status event with an error status might indicate that an
  // async command failed. We use this to unregister async command handlers
  // below.
  bool unregister_async_handler = false;

  if (event->event_code() == hci_spec::kCommandCompleteEventCode) {
    const hci_spec::CommandCompleteEventParams& params =
        event->params<hci_spec::CommandCompleteEventParams>();
    matching_opcode = le16toh(params.command_opcode);
    allowed_command_packets_.Set(params.num_hci_command_packets);
  } else {  //  hci_spec::kCommandStatusEventCode
    const hci_spec::CommandStatusEventParams& params =
        event->params<hci_spec::CommandStatusEventParams>();
    matching_opcode = le16toh(params.command_opcode);
    allowed_command_packets_.Set(params.num_hci_command_packets);
    unregister_async_handler =
        params.status != pw::bluetooth::emboss::StatusCode::SUCCESS;
  }
  bt_log(TRACE,
         "hci",
         "allowed packets update: %zu",
         allowed_command_packets_.value());

  if (matching_opcode == hci_spec::kNoOp) {
    return;
  }

  auto it = pending_transactions_.find(matching_opcode);
  if (it == pending_transactions_.end()) {
    bt_log(
        ERROR, "hci", "update for unexpected opcode: %#.4x", matching_opcode);
    return;
  }

  std::unique_ptr<TransactionData>& transaction_ref = it->second;
  BT_DEBUG_ASSERT(transaction_ref->opcode() == matching_opcode);

  // If the command is synchronous or there's no handler to cleanup, we're done.
  if (transaction_ref->handler_id() == 0u) {
    std::unique_ptr<TransactionData> transaction = std::move(it->second);
    pending_transactions_.erase(it);
    transaction->Complete(std::move(event));
    return;
  }

  // TODO(fxbug.dev/42062242): Do not allow asynchronous commands to finish with
  // Command Complete.
  if (event_code == hci_spec::kCommandCompleteEventCode) {
    bt_log(WARN, "hci", "async command received CommandComplete");
    unregister_async_handler = true;
  }

  // If an asynchronous command failed, then remove its event handler.
  if (unregister_async_handler) {
    bt_log(TRACE, "hci", "async command failed; removing its handler");
    RemoveEventHandlerInternal(transaction_ref->handler_id());
    std::unique_ptr<TransactionData> transaction = std::move(it->second);
    pending_transactions_.erase(it);
    transaction->Complete(std::move(event));
  } else {
    // Send the status event to the async transaction.
    transaction_ref->Complete(std::move(event));
  }
}

void CommandChannel::NotifyEventHandler(std::unique_ptr<EventPacket> event) {
  struct PendingCallback {
    EventCallbackVariant callback_variant;
    EventHandlerId handler_id;
  };
  std::vector<PendingCallback> pending_callbacks;

  hci_spec::EventCode event_code;
  const std::unordered_multimap<hci_spec::EventCode, EventHandlerId>*
      event_handlers;

  EventType event_type;
  switch (event->event_code()) {
    case hci_spec::kLEMetaEventCode:
      event_type = EventType::kLEMetaEvent;
      event_code = event->params<hci_spec::LEMetaEventParams>().subevent_code;
      event_handlers = &le_meta_subevent_code_handlers_;
      break;
    case hci_spec::kVendorDebugEventCode:
      event_type = EventType::kVendorEvent;
      event_code = pw::bluetooth::emboss::MakeVendorDebugEventView(
                       event->view().data().data(), event->view().size())
                       .subevent_code()
                       .Read();
      event_handlers = &vendor_subevent_code_handlers_;
      break;
    default:
      event_type = EventType::kHciEvent;
      event_code = event->event_code();
      event_handlers = &event_code_handlers_;
      break;
  }

  auto range = event_handlers->equal_range(event_code);
  if (range.first == range.second) {
    bt_log(DEBUG,
           "hci",
           "%s event %#.2x received with no handler",
           EventTypeToString(event_type).c_str(),
           event_code);
    return;
  }

  auto iter = range.first;
  while (iter != range.second) {
    EventHandlerId event_id = iter->second;
    bt_log(TRACE,
           "hci",
           "notifying handler (id %zu) for event code %#.2x",
           event_id,
           event_code);
    auto handler_iter = event_handler_id_map_.find(event_id);
    BT_DEBUG_ASSERT(handler_iter != event_handler_id_map_.end());

    EventHandlerData& handler = handler_iter->second;
    BT_DEBUG_ASSERT(handler.event_code == event_code);

    std::visit(
        [&pending_callbacks, event_id](auto& callback) {
          pending_callbacks.push_back({callback.share(), event_id});
        },
        handler.event_callback);

    ++iter;  // Advance so we don't point to an invalid iterator.
    if (handler.is_async()) {
      bt_log(TRACE,
             "hci",
             "removing completed async handler (id %zu, event code: %#.2x)",
             event_id,
             event_code);
      pending_transactions_.erase(handler.pending_opcode);
      RemoveEventHandlerInternal(event_id);  // |handler| is now dangling.
    }
  }

  // Process queue so callbacks can't add a handler if another queued command
  // finishes on the same event.
  TrySendQueuedCommands();

  EventPacket& event_packet = *event;
  for (auto it = pending_callbacks.begin(); it != pending_callbacks.end();
       ++it) {
    // Execute the event callback.
    EventCallbackResult result = std::visit(
        overloaded{[&event_packet](EventCallback& callback) {
                     return callback(event_packet);
                   },
                   [&event_packet](EmbossEventCallback& callback) {
                     auto emboss_packet =
                         EmbossEventPacket::New(event_packet.view().size());
                     bt::MutableBufferView dest = emboss_packet.mutable_data();
                     event_packet.view().data().Copy(&dest);
                     return callback(emboss_packet);
                   }},
        it->callback_variant);

    if (result == EventCallbackResult::kRemove) {
      RemoveEventHandler(it->handler_id);
    }
  }
}

void CommandChannel::OnEvent(pw::span<const std::byte> buffer) {
  if (!active_) {
    bt_log(INFO, "hci", "ignoring event (CommandChannel is inactive)");
    return;
  }

  if (buffer.size() < sizeof(hci_spec::EventHeader)) {
    // TODO(fxbug.dev/42179582): Handle these types of errors by signaling
    // Transport.
    bt_log(ERROR,
           "hci",
           "malformed packet - expected at least %zu bytes, got %zu",
           sizeof(hci_spec::EventHeader),
           buffer.size());
    return;
  }

  const size_t payload_size = buffer.size() - sizeof(hci_spec::EventHeader);

  std::unique_ptr<EventPacket> event = EventPacket::New(payload_size);
  event->mutable_view()->mutable_data().Write(
      reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size());
  event->InitializeFromBuffer();

  if (event->view().header().parameter_total_size != payload_size) {
    // TODO(fxbug.dev/42179582): Handle these types of errors by signaling
    // Transport.
    bt_log(ERROR,
           "hci",
           "malformed packet - payload size from header (%hu) does not match"
           " received payload size: %zu",
           event->view().header().parameter_total_size,
           payload_size);
    return;
  }

  if (event->event_code() == hci_spec::kCommandStatusEventCode ||
      event->event_code() == hci_spec::kCommandCompleteEventCode) {
    UpdateTransaction(std::move(event));
    TrySendQueuedCommands();
  } else {
    NotifyEventHandler(std::move(event));
  }
}

void CommandChannel::OnCommandTimeout(TransactionId transaction_id) {
  if (!active_) {
    return;
  }
  bt_log(
      ERROR, "hci", "command %zu timed out, notifying error", transaction_id);
  active_ = false;
  if (channel_timeout_cb_) {
    fit::closure cb = std::move(channel_timeout_cb_);
    // The callback may destroy CommandChannel, so no state should be accessed
    // after this line.
    cb();
  }
}

void CommandChannel::AttachInspect(inspect::Node& parent,
                                   const std::string& name) {
  command_channel_node_ = parent.CreateChild(name);
  next_transaction_id_.AttachInspect(command_channel_node_,
                                     "next_transaction_id");
  next_event_handler_id_.AttachInspect(command_channel_node_,
                                       "next_event_handler_id");
  allowed_command_packets_.AttachInspect(command_channel_node_,
                                         "allowed_command_packets");
}

}  // namespace bt::hci
