#pragma once
// On-device channel Add/Edit form (Messages > Channels > "+ Add channel" / Edit).
// Owned by MessagesScreen, which delegates render()/handleInput() to it while
// active() — the same relationship WaypointsView has with TrailScreen.
//
// "+ Add channel" starts with a Type picker (Public/Hashtag/Private), matching
// the phone app's channel-type trichotomy (docs/companion_protocol.md's
// "Channel Types") instead of exposing the raw Name+Secret form directly:
//   - Public: one tap re-adds the well-known default channel (same secret
//     MyMesh.cpp pre-configures at first boot) — no fields to fill in.
//   - Hashtag: type just a topic name; the channel name and secret are both
//     derived from it ("#topic", secret = first 16 bytes of sha256("#topic")).
//     A topic-based public group chat, discoverable without knowing the hash
//     convention exists.
//   - Private: today's manual Name + Secret form (see below) — a channel only
//     those you share the secret with can join.
// Editing an existing channel (openEdit()) skips the Type picker and goes
// straight to the Name+Secret form — there's no ambiguity to resolve there.
//
// The Secret field (Private, and internally for Public/Hashtag) toggles
// between two entry modes with LEFT/RIGHT:
//   - Passphrase (default): any text, SHA-256'd down to 16 bytes — the same
//     primitive BaseChatMesh::addChannel()/setChannel() already use to derive
//     a channel's routing hash, just applied here to a typed phrase instead of
//     a raw key. Easiest to agree on verbally, like a room password.
//   - Hex key: the exact 32-hex-char (128-bit) secret, e.g. from a channel QR
//     (see docs/qr_codes.md) — for joining a channel whose precise secret you
//     were given rather than agreeing on a new passphrase.
// Either way only a 16-byte (128-bit) secret is produced, matching the
// existing CMD_GET_CHANNEL comment ("NOTE: only 128-bit supported").

#include <Utils.h>

class ChannelsView {
  UITask* _task;

  enum Mode : uint8_t { OFF, TYPE_PICK, ADD, ADD_HASHTAG, EDIT };
  uint8_t _mode = OFF;
  int     _idx = -1;              // channel slot being added/edited

  int  _type_sel = 0;              // TYPE_PICK cursor: 0=Public 1=Hashtag 2=Private

  char _name[32] = "";
  bool _hex_mode = false;         // false = passphrase, true = 32-hex-char raw key
  char _secret_text[33] = "";     // passphrase or hex string typed so far
  char _topic[31] = "";           // ADD_HASHTAG: topic without the leading '#'

  int  _sel = 0;                  // 0=Name 1=Secret 2=[Save] (ADD_HASHTAG: 0=Topic 1=[Save])
  bool _kb_active = false;
  int  _kb_field = -1;            // 0=Name, 1=Secret, 2=Topic

  KeyboardWidget& kb() { return _task->keyboard(); }

  void openKb(const char* initial, int max) {
    kb().begin(initial, max);
    kb().clearPlaceholders();     // literal field — {loc}/{time} make no sense here
    _kb_active = true;
  }

  // Parses exactly 32 hex chars into a 16-byte secret (zero-padded to 32 for
  // ChannelDetails.secret's full field width). Rejects malformed hex and the
  // all-zero sentinel setChannelLocal() reads as "slot deleted" (see
  // isAllZero() in MyMesh.cpp) -- saving one would pass, then immediately
  // trigger onChannelRemoved() on this very slot, silently discarding the
  // channel and its bot/notif/favourite state. Shared by deriveSecret()'s
  // hex-mode branch and the Public quick-add (a fixed, known-good constant,
  // but parsed through the same path rather than duplicated).
  static bool hexToSecret(const char* hex32, uint8_t out[32]) {
    if (strlen(hex32) != 32) return false;
    uint8_t tmp[16];
    for (int i = 0; i < 16; i++) {
      char byte_str[3] = { hex32[i * 2], hex32[i * 2 + 1], 0 };
      char* end = nullptr;
      long v = strtol(byte_str, &end, 16);
      if (*end != 0) return false;
      tmp[i] = (uint8_t)v;
    }
    bool all_zero = true;
    for (int i = 0; i < 16 && all_zero; i++) if (tmp[i]) all_zero = false;
    if (all_zero) return false;
    memset(out, 0, 32);
    memcpy(out, tmp, 16);
    return true;
  }

  // Turn the typed Secret field into a 16-byte channel secret. Returns false
  // (and leaves out[] untouched) if the field can't be parsed as configured.
  bool deriveSecret(uint8_t out[32]) const {
    if (_hex_mode) return hexToSecret(_secret_text, out);
    if (_secret_text[0] == '\0') return false;   // blank passphrase not allowed
    uint8_t digest[32];
    mesh::Utils::sha256(digest, sizeof(digest), (const uint8_t*)_secret_text, (int)strlen(_secret_text));
    memset(out, 0, 32);
    memcpy(out, digest, 16);
    return true;
  }

  bool saveChannel(const char* name, const uint8_t secret[32]) {
    ChannelDetails ch;
    memset(&ch, 0, sizeof(ch));
    strncpy(ch.name, name, sizeof(ch.name) - 1);
    memcpy(ch.channel.secret, secret, sizeof(ch.channel.secret));
    return the_mesh.setChannelLocal((uint8_t)_idx, ch);
  }

  // Type picker's Public row: no fields to fill in, so this commits directly
  // instead of dropping into the Name+Secret form -- the well-known channel's
  // name and secret are both fixed. Hex confirmed via
  // `python3 -c "import base64; print(base64.b64decode('izOH6cXN6mrJ5e26oRXNcg==').hex())"`
  // to match MyMesh.cpp's PUBLIC_GROUP_PSK (base64) and
  // docs/companion_protocol.md's documented public channel key (hex).
  void commitPublic() {
    static const char PUBLIC_SECRET_HEX[] = "8b3387e9c5cdea6ac9e5edbaa115cd72";
    uint8_t secret[32];
    hexToSecret(PUBLIC_SECRET_HEX, secret);   // fixed, known-good constant -- can't fail
    if (saveChannel("Public", secret)) _task->showAlert("Channel added", 1000);
    else                               _task->showAlert("Save failed", 1200);
    _mode = OFF;
  }

  void commit() {
    if (_mode == ADD_HASHTAG) {
      if (_topic[0] == '\0') { _task->showAlert("Topic required", 1200); return; }
      // "#topic" as both the channel's display name and the passphrase fed to
      // sha256 -- the exact "Hashtag Channels" convention in
      // docs/companion_protocol.md, just synthesized instead of hand-typed.
      snprintf(_name, sizeof(_name), "#%s", _topic);
      strncpy(_secret_text, _name, sizeof(_secret_text) - 1);
      _secret_text[sizeof(_secret_text) - 1] = '\0';
      _hex_mode = false;
    }
    if (_name[0] == '\0') { _task->showAlert("Name required", 1200); return; }
    uint8_t secret[32];
    if (!deriveSecret(secret)) {
      _task->showAlert(_hex_mode ? "Invalid secret" : "Secret required", 1400);
      return;
    }
    if (saveChannel(_name, secret)) {
      _task->showAlert((_mode == ADD || _mode == ADD_HASHTAG) ? "Channel added" : "Channel updated", 1000);
      _mode = OFF;
    } else {
      _task->showAlert("Save failed", 1200);
    }
  }

public:
  explicit ChannelsView(UITask* task) : _task(task) {}

  bool active() const { return _mode != OFF || _kb_active; }
  void reset() { _mode = OFF; _kb_active = false; _kb_field = -1; _type_sel = 0; _topic[0] = '\0'; }

  // idx = the slot to write on Save (first free slot for Add, existing slot
  // for Edit). Starts at the Type picker (Public/Hashtag/Private) -- see
  // handleInput()'s TYPE_PICK branch for what each option does next.
  void openAdd(int idx) {
    _mode = TYPE_PICK; _idx = idx; _type_sel = 0;
  }
  // Private option's form: today's manual Name + Secret entry, unchanged.
  void openPrivate() {
    _mode = ADD;
    _name[0] = '\0'; _secret_text[0] = '\0'; _hex_mode = false; _sel = 0;
  }
  void openEdit(int idx, const char* existing_name) {
    _mode = EDIT; _idx = idx;
    strncpy(_name, existing_name, sizeof(_name) - 1); _name[sizeof(_name) - 1] = '\0';
    _secret_text[0] = '\0'; _hex_mode = false; _sel = 0;
  }

  int render(DisplayDriver& display) {
    display.setTextSize(1);
    display.setColor(DisplayDriver::LIGHT);
    if (_kb_active) return kb().render(display);

    const int top  = display.listStart();
    const int step = display.lineStep();

    if (_mode == TYPE_PICK) {
      display.drawCenteredHeader("ADD CHANNEL");
      static const char* TYPE_LABELS[3] = { "Public", "Hashtag", "Private" };
      for (int i = 0; i < 3; i++) {
        int y = top + i * step;
        display.drawSelectionRow(0, y - 1, display.width(), step - 1, i == _type_sel);
        display.drawTextEllipsized(2, y, display.width() - 4, TYPE_LABELS[i]);
        display.setColor(DisplayDriver::LIGHT);
      }
      return 1000;
    }

    if (_mode == ADD_HASHTAG) {
      display.drawCenteredHeader("ADD CHANNEL");
      for (int i = 0; i < 2; i++) {
        int y = top + i * step;
        bool sel = (i == _sel);
        display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
        char row[40];
        if (i == 0) snprintf(row, sizeof(row), "Topic: %s", _topic[0] ? _topic : "(none)");
        else        snprintf(row, sizeof(row), "[Save]");
        display.drawTextEllipsized(2, y, display.width() - 4, row);
        display.setColor(DisplayDriver::LIGHT);
      }
      return 1000;
    }

    display.drawCenteredHeader(_mode == ADD ? "ADD CHANNEL" : "EDIT CHANNEL");
    for (int i = 0; i < 3; i++) {
      int y = top + i * step;
      bool sel = (i == _sel);
      display.drawSelectionRow(0, y - 1, display.width(), step - 1, sel);
      char row[40];
      if (i == 0)      snprintf(row, sizeof(row), "Name: %s", _name[0] ? _name : "(none)");
      else if (i == 1) snprintf(row, sizeof(row), "Secret (%s): %s",
                                _hex_mode ? "hex" : "phrase", _secret_text[0] ? _secret_text : "(none)");
      else             snprintf(row, sizeof(row), "[Save]");
      // Ellipsize rather than print() directly -- a long name/secret must not
      // wrap onto the next row's line (print() wraps by default).
      display.drawTextEllipsized(2, y, display.width() - 4, row);
      display.setColor(DisplayDriver::LIGHT);
    }
    return 1000;
  }

  bool handleInput(char c) {
    if (_kb_active) {
      auto r = kb().handleInput(c);
      if (r == KeyboardWidget::DONE) {
        if      (_kb_field == 0) { strncpy(_name, kb().buf, sizeof(_name) - 1); _name[sizeof(_name) - 1] = '\0'; }
        else if (_kb_field == 1) { strncpy(_secret_text, kb().buf, sizeof(_secret_text) - 1); _secret_text[sizeof(_secret_text) - 1] = '\0'; }
        else                     { strncpy(_topic, kb().buf, sizeof(_topic) - 1); _topic[sizeof(_topic) - 1] = '\0'; }
        _kb_active = false; _kb_field = -1;
      } else if (r == KeyboardWidget::CANCELLED) {
        _kb_active = false; _kb_field = -1;
      }
      return true;
    }

    if (_mode == TYPE_PICK) {
      if (c == KEY_CANCEL) { _mode = OFF; return true; }
      if (c == KEY_UP)   { _type_sel = (_type_sel > 0) ? _type_sel - 1 : 2; return true; }
      if (c == KEY_DOWN) { _type_sel = (_type_sel < 2) ? _type_sel + 1 : 0; return true; }
      if (c == KEY_ENTER) {
        if      (_type_sel == 0) commitPublic();
        else if (_type_sel == 1) { _mode = ADD_HASHTAG; _sel = 0; _topic[0] = '\0'; }
        else                     openPrivate();
      }
      return true;
    }

    if (_mode == ADD_HASHTAG) {
      if (c == KEY_CANCEL) { _mode = OFF; return true; }
      if (c == KEY_UP)   { _sel = (_sel > 0) ? _sel - 1 : 1; return true; }
      if (c == KEY_DOWN) { _sel = (_sel < 1) ? _sel + 1 : 0; return true; }
      if (c == KEY_ENTER) {
        if (_sel == 0) { _kb_field = 2; openKb(_topic, sizeof(_topic) - 1); }
        else            commit();
      }
      return true;
    }

    if (c == KEY_CANCEL) { _mode = OFF; return true; }
    if (c == KEY_UP)   { _sel = (_sel > 0) ? _sel - 1 : 2; return true; }
    if (c == KEY_DOWN) { _sel = (_sel < 2) ? _sel + 1 : 0; return true; }
    if ((keyIsPrev(c) || keyIsNext(c)) && _sel == 1) {
      _hex_mode = !_hex_mode;
      _secret_text[0] = '\0';   // switching mode invalidates whatever was typed
      return true;
    }
    if (c == KEY_ENTER) {
      if      (_sel == 0) { _kb_field = 0; openKb(_name, sizeof(_name) - 1); }
      else if (_sel == 1) { _kb_field = 1; openKb(_secret_text, _hex_mode ? 32 : (int)sizeof(_secret_text) - 1); }
      else                 commit();
      return true;
    }
    return true;
  }
};
