struct Sec_Hover_Info {
  Byte_Range rng;
  // A string tree where children are more indented than parents
  String8_Node *desc;
};

template <typename T> T bswap_if_needed(T x) { return x; }
template <> u16 bswap_if_needed(u16 x) { return bswap(x); }
template <> u32 bswap_if_needed(u32 x) { return bswap(x); }
template <> u64 bswap_if_needed(u64 x) { return bswap(x); }

template <typename T> 
String8_Node *hover_display_val_be(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  static_assert(!std::is_same_v<T, String8>);
  val = bswap_if_needed(val);
  return push_str8_node_child(arena, prev, fmt, val);
}

String8_Node *hover_display_val_str8(Arena *arena, String8_Node *prev, const char *fmt, String8 val)
{
  return push_str8_node_child(arena, prev, fmt, val.str ? val.c() : "");
}

template <typename T> 
String8_Node *hover_display_val_le(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  return push_str8_node_child(arena, prev, fmt, val);
}

template <typename T> 
String8_Node *hover_display_val_le_abs(Arena *arena, String8_Node *prev, const char *fmt, T val)
{
  return push_str8_node_child(arena, prev, fmt, std::abs(val));
}

internal
String8_Node *hover_display_datetime_str(Arena *arena, String8_Node *prev, const char *fmt_pre, u32 datetime)
{
  datetime = bswap(datetime);
  
  // datetime:
  // year (6b) | month (4b) | day (5b) | hour (5b) | min (6b) | sec (6b)
  u32 year = (datetime >> 26) + 1995;
  u32 month = ((datetime & 0x3ff'ffff) >> 22);
  u32 day = (datetime & 0x3f'ffff) >> 17;
  u32 hour = (datetime & 0x1'ffff) >> 12;
  u32 min = (datetime & 0xfff) >> 6;
  u32 sec = datetime & 0x3f;
  return push_str8_node_child(arena, prev, "%s%u/%02u/%02u %02u:%02u:%02u", fmt_pre, year, month, day, hour, min, sec);
}

internal
String8_Node *display_val_rootzip(Arena *arena, String8_Node *prev, const char *fmt, const u8 *src)
{ 
  const u8 Z_DEFLATED = 8;

  String8 zip_method;
  if (src[0] == 'Z' && src[1] == 'L' && src[2] == Z_DEFLATED) {
    zip_method = str8("ZLIB");
  } else if (src[0] == 'C' && src[1] == 'S' && src[2] == Z_DEFLATED) {
    zip_method = str8("Old");
  } else if (src[0] == 'X' && src[1] == 'Z' && src[2] == 0) {
    zip_method = str8("LZMA");
  } else if (src[0] == 'L' && src[1] == '4') {
    zip_method = str8("LZ4");
  } else if (src[0] == 'Z' && src[1] == 'S' && src[2] == 1) {
    zip_method = str8("ZSTD");
  } else {
    return nullptr;
  }
  u32 comp_size = src[3] | (src[4] << 8) | (src[5] << 16);
  u32 uncomp_size = src[6] | (src[7] << 8) | (src[8] << 16);

  String8_Node *sn = push_str8_node_child(arena, prev, "%s", fmt);
  sn = push_str8_node_child(arena, sn, "Zip method: %s", zip_method.c());
  sn = push_str8_node(arena, sn, "Compressed size: %s", to_pretty_size(arena, comp_size).c());
  sn = push_str8_node(arena, sn, "Uncompressed size: %s", to_pretty_size(arena, uncomp_size).c());
  sn = push_str8_node(arena, sn, "Comp. ratio: %.2f", (f32)comp_size / uncomp_size);

  return sn;
}

template <typename T>
using Display_Fn = String8_Node *(*)(Arena *, String8_Node *, const char *, T);

template <typename T>
internal
T read_buf(const void *buf, u64 &off)
{
  T val;
  memcpy(&val, (u8 *)buf + off, sizeof(val));
  off += sizeof(val);
  return val;
}

// Functor used by get_section_hover_info to describe the structure of a section and print data about it.
struct Sec_Hover_Fn {
  u64 start; // the start of the section (including the pre_size, e.g. the TKey)
  u64 roff; // the offset relative to the section start
  const u8 *data; // the entire file data
  Arena *arena;
  Sec_Hover_Info &info;
  u64 &cur_field_off;
  b8 display_grouped;

  template <typename F>
  b8 titled_section(const char *title, F &&fn) const
  {
    String8_Node *prev_desc = info.desc;
    info.desc = push_str8_node_child(arena, prev_desc, title);

    b8 hovered = fn();

    if (!hovered) {
      pop_str8_node_child(prev_desc, info.desc);
      info.desc = prev_desc;
    }
    return hovered;
  }

  // Returns true if this field is being hovered
  template <typename T>
  b8 field(const char *desc_fmt, Display_Fn<T> display_val, T *val_read = nullptr) const
  {
    static_assert(!std::is_same_v<T, String8>, "use field_str8 instead.");
    u64 field_len = sizeof(T);
    u64 field_off = cur_field_off;
    cur_field_off += field_len;
    if (display_grouped || roff < field_off + field_len) {
      info.rng = { start + field_off, field_len };
      T val = read_buf<T>(data + start, field_off);
      display_val(arena, info.desc, desc_fmt, val);
      if (val_read)
        *val_read = val;
      return !display_grouped;
    }
    return false;
  }

  template <typename TStrSize>
  b8 field_str8(const char *desc_fmt, Display_Fn<String8> display_val = hover_display_val_str8) const
  {
    // String size can be stored as different types, like u8 (by ROOT I/O) or u32 (by RNTuple).
    TStrSize str_size;
    memcpy(&str_size, data + start + cur_field_off, sizeof(TStrSize));
    u64 field_off = cur_field_off;
    u64 field_len = sizeof(TStrSize) + (u64)str_size;
    cur_field_off += field_len;
    if (display_grouped || roff < field_off + field_len) {
      info.rng = { start + field_off, field_len };
      u8 *buf = arena_push_array_nozero<u8>(arena, str_size + 1);
      memcpy(buf, data + start + field_off + sizeof(TStrSize), str_size);
      buf[str_size] = 0;
      String8 s = { buf, str_size };
      display_val(arena, info.desc, desc_fmt, s);
      return !display_grouped;
    }
    return false;
  }

  template <typename T>
  b8 field_be(const char *desc_fmt) const
  {
    return field<T>(desc_fmt, hover_display_val_be<T>);
  }

  template <typename T>
  b8 field_le(const char *desc_fmt) const
  {
    return field<T>(desc_fmt, hover_display_val_le<T>);
  }

  b8 range(const char *desc, u64 range_len) const
  {
    if (roff < cur_field_off + range_len) {
      info.rng = { start + cur_field_off, range_len };
      push_str8_node_child(arena, info.desc, "%s", desc);
      return true;
    }
    cur_field_off += range_len;
    return false;
  }

  b8 range_data(const char *desc, u64 range_len, Display_Fn<const u8 *> display_val) const
  {
    if (roff < cur_field_off + range_len) {
      info.rng = { start + cur_field_off, range_len };
      display_val(arena, info.desc, desc, data + start + cur_field_off);
      return true;
    }
    cur_field_off += range_len;
    return false;
  }

  b8 maybe_rootzip(b8 *was_zipped = nullptr) const
  {
    // TODO boundary checks
    const u64 range_len = 9;
    if (display_val_rootzip(arena, info.desc, "Zipped Block", data + start + cur_field_off)) {
      if (was_zipped) *was_zipped = true;
      if (roff < cur_field_off + range_len) {
        info.rng = { start + cur_field_off, range_len };
        hover_display_val_be(arena, info.desc, "", data + start + cur_field_off);
        return true;
      }
      // discard the description (it's fine since it's allocated in the scratch arena)
      if (info.desc->first_child == info.desc->last_child) {
        info.desc->first_child = info.desc->last_child = nullptr;
      } else {
        info.desc->last_child = info.desc->last_child->prev;
      }
      cur_field_off += range_len;
    } else if (was_zipped) {
      *was_zipped = false;
    }
    return false;
  }

  b8 tkey() const
  {
    return titled_section("TKey", [this] {
      u16 version_be;
      memcpy(&version_be, data + start + 4, sizeof(u16));
      u32 version = bswap(version_be);
      b8 is_big = version > 1000;

      if (is_big) {
        return field_be<u32>("NBytes: %u")
        || field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
           x = bswap(x);
           x -= 1000; 
           return push_str8_node_child(arena, prev, fmt, x);
        })
        || field_be<u32>("Obj Len: %u")
        || field<u32>("Datetime: ", hover_display_datetime_str)
        || field_be<u16>("Key Len: %u") 
        || field_be<u16>("Cycle: %u") 
        || field_be<u64>("Seek Key: 0x%" PRIX64) 
        || field_be<u64>("Seek Pdir: 0x%" PRIX64) 
        || field_str8<u8>("Class Name: %s")
        || field_str8<u8>("Obj Name: %s")
        || field_str8<u8>("Obj Title: %s")
        ;
      } else {
        return field_be<u32>("NBytes: %u")
        || field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
           x = bswap(x);
           return push_str8_node_child(arena, prev, fmt, x);
        })
        || field_be<u32>("Obj Len: %u")
        || field<u32>("Datetime: ", hover_display_datetime_str)
        || field_be<u16>("Key Len: %u") 
        || field_be<u16>("Cycle: %u") 
        || field_be<u32>("Seek Key: 0x%" PRIX64) 
        || field_be<u32>("Seek Pdir: 0x%" PRIX64) 
        || field_str8<u8>("Class Name: %s")
        || field_str8<u8>("Obj Name: %s")
        || field_str8<u8>("Obj Title: %s")
        ;
      }
    });
  }

  b8 envelope_preamble() const
  {
    static const char *const envelope_names[] = { "INVALID", "Header", "Footer", "Page List" };
    return titled_section("Envelope Preamble", [this] {
      return field<u16>("Envelope type: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 val) {
            const char *name = (val >= countof(envelope_names)) ? "Unknown" : envelope_names[val];
            return push_str8_node_child(arena, prev, fmt, name);
          })
          || range_data("Envelope size: %s", 6, [] (Arena *arena, String8_Node *prev, const char *fmt, const u8 *payload) {
            u64 size;
            memcpy(&size, payload, 6);
            return push_str8_node_child(arena, prev, fmt, to_pretty_size(arena, size));
          });
    });
  }

  b8 frame_header(const char *title = nullptr) const
  {
    String8 titlestr = title ? push_str8f(arena, "Frame Header: %s", title) : str8("Frame Header");
    return titled_section(titlestr.c(), [this] {
      i64 size;
      memcpy(&size, data + start + cur_field_off, sizeof(size));
      if (size >= 0)
        return field<i64>("Record frame size: %" PRIi64 " B", hover_display_val_le_abs<i64>);
      else if (roff < cur_field_off + 12) {
        info.rng = { start + cur_field_off, 12 };
        b8 ok = titled_section("List Frame", [this] {
          return field<i64>("Size: %" PRIi64 " B", hover_display_val_le_abs<i64>)
              || field_le<u32>("N Items: %u")
              ;
        });
        if (ok)
          return ok;
      }
      cur_field_off += 12;
      return false;
    });
  }

  b8 field_desc() const
  {
    static const char *const field_struct_names[] = {
      "Leaf", "Collection", "Record", "Variant", "Unsplit"
    };

    i64 size;
    memcpy(&size, data + start + cur_field_off, sizeof(size));
    u64 field_desc_len = (u64)std::abs(size);
    if (roff < cur_field_off + field_desc_len) {
      info.rng = { start + cur_field_off, (u64)field_desc_len };
      return titled_section("Field", [this] {
        b8 ok = field_le<i64>("Size: %" PRIi64 " B")
            || field_le<u32>("Field version: %u")
            || field_le<u32>("Type version: %u")
            || field_le<u32>("On-disk parent id: %u")
            || field<u16>("Field structure: %s", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 type) {
              const char *name = (type >= countof(field_struct_names)) ? "Unknown" : field_struct_names[type];
              return push_str8_node_child(arena, prev, fmt, name);
            });
        if (ok)
          return true;

        u16 flags;
        ok = field<u16>("Flags: 0b%b", hover_display_val_le, &flags);

        if (flags & RNTupleSerializer::kFlagRepetitiveField)
          ok = ok || field_le<u64>("N Repetitions: %" PRIu64);
        if (flags & RNTupleSerializer::kFlagProjectedField)
          ok = ok || field_le<u32>("On disk proj.src id: %u");
        if (flags & RNTupleSerializer::kFlagHasTypeChecksum)
          ok = ok || field_le<u32>("Checksum: %u");

        ok = ok || field_str8<u32>("Name: %s")
                || field_str8<u32>("Type Name: %s")
                || field_str8<u32>("Type Alias: %s")
                || field_str8<u32>("Description: %s")
                ;
        return ok;
      });
    }
    cur_field_off += field_desc_len;
    return false;
  }

  b8 column_desc(const char *title) const
  {
    i64 size;
    memcpy(&size, data + start + cur_field_off, sizeof(size));
    u64 col_desc_len = (u64)std::abs(size);
    if (roff < cur_field_off + col_desc_len) {
      info.rng = { start + cur_field_off, col_desc_len };
      return titled_section(title, [this] {
        b8 ok = field_le<i64>("Size: %" PRIi64 " B")
            || field<u16>("Column type: %s", [](Arena *arena, String8_Node *prev, const char *fmt, u16 val) {
              const char *readable_col_type = get_column_type_name(val);
              return push_str8_node_child(arena, prev, fmt, readable_col_type);
            })
            || field_le<u16>("Bits on storage: %u")
            || field_le<u32>("Field ID: %u")
            ; 
        if (ok)
          return true;

        u16 flags;
        ok = field<u16>("Flags: 0b%b", hover_display_val_le, &flags); 
        ok = ok || field_le<u16>("Representation idx: %u");
        if (flags & RNTupleSerializer::kFlagDeferredColumn)
          ok = ok || field_le<u64>("First element: %" PRIu64);
        if (flags & RNTupleSerializer::kFlagHasValueRange) {
          ok = ok || field_le<double>("Value Min: %f")
                  || field_le<double>("Value Max: %f");
        }
        return ok;
      });
    }
    cur_field_off += col_desc_len;
    return false;
  }

  template <typename F>
  b8 list_frame(const char *title, F &&fn) const
  {
    if (frame_header(title))
      return true;
    // we need to read back the number of entries to know how long is the next section.
    u64 n_elems_off = cur_field_off - sizeof(u32);
    u32 n_elems;
    memcpy(&n_elems, data + start + n_elems_off, sizeof(n_elems));
    for (u32 i = 0; i < n_elems; ++i)
      if (fn())
        return true;
    return false;
  }

  b8 schema_description(const char *title) const
  {
    return titled_section(title, [this] {
      // TODO: Columns and alias columns are not the same
      return list_frame("Fields", [this] { return field_desc(); })
          || list_frame("Columns", [this] { return column_desc("Column"); })
          || list_frame("Alias Columns", [this] { return column_desc("Alias Column"); })
          || list_frame("Extra Type Infos", [this] {
            return field_le<u32>("Content identifier: %lu")
                || field_le<u32>("Type version from: %lu")
                || field_le<u32>("Type version to: %lu");
          });     
    });
  }

  b8 locator(const char *title) const
  {
    // TODO
    return titled_section(title, [this] {
      return true;
    });
  }

  b8 cluster_group() const
  {
    return titled_section("Cluster Group", [this] {
      return frame_header()
          || field_le<u64>("Min Entry: %" PRIu64)
          || field_le<u64>("Entry Span: %" PRIu64)
          || field_le<u32>("N Clusters: %u")
          || field_le<u64>("Env.Link Len: %" PRIu64)
          || locator("Env.Link Locator")
          ;
    });
  }
};

// `off` is the absolute offset into `data`.
internal
Sec_Hover_Info get_section_hover_info(Arena *arena, Section section, u64 off, const u8 *data, b8 display_grouped)
{
  Sec_Hover_Info info {};

  // printf("off: 0x%" PRIX64 ", sec start - pre_size: (0x%" PRIX64 " - %" PRIu64 ") = 0x%" PRIX64 "\n", off, section.range.start, section.pre_size, section.range.start - section.pre_size);
  assert(off >= section.range.start - section.pre_size);
  
  // Hover info header
  String8 sec_name = section_names[section.id];
  if (section.id == Sec_Page && section.info) {
    Page_Info_Node *pinfo = (Page_Info_Node *)section.info;
    info.desc = push_str8_node(arena, nullptr, "%s [%s]", 
                               sec_name.c(), pinfo->elem_type_name.c());
    push_str8_node_child(arena, info.desc, "Field: %s", pinfo->owner_field_name.c());
    push_str8_node_child(arena, info.desc, "N. Elems: %d", abs(pinfo->n_elems));
    push_str8_node_child(arena, info.desc, "Bits per elem: %u", pinfo->bits_per_elem);
    push_str8_node_child(arena, info.desc, "-----------");
  } else {
    info.desc = push_str8_node(arena, nullptr, "%s", sec_name.c());
  }

  u64 start = section.range.start - section.pre_size;
  u64 roff = off - start; // offset relative to `section`
  u64 cur_field_off = 0;
  Sec_Hover_Fn hover { start, roff, data, arena, info, cur_field_off, display_grouped };
  
  switch (section.id) {
  case Sec_RNTuple_Anchor: {
    hover.tkey()
    || hover.field<u32>("Object len: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { 
      x = bswap(x);
      x -= 0x4000'0000;
      return push_str8_node_child(arena, prev, fmt, x);
    })
    || hover.field_be<u16>("Class version: %u")
    || hover.field_be<u16>("Version Epoch: %u")
    || hover.field_be<u16>("Version Major: %u")
    || hover.field_be<u16>("Version Minor: %u")
    || hover.field_be<u16>("Version Patch: %u")
    || hover.field_be<u64>("Seek Header: 0x%" PRIX64)
    || hover.field_be<u64>("NBytes Header: %u")
    || hover.field_be<u64>("Len Header: %u")
    || hover.field_be<u64>("Seek Footer: 0x%" PRIX64)
    || hover.field_be<u64>("NBytes Footer: %u")
    || hover.field_be<u64>("Len Footer: %u")
    || hover.field_be<u64>("Max Key Size: %u")
    || hover.field_le<u64>("Checksum: 0x%" PRIX64)
    ;   
  } break;
  
  case Sec_TFile_Header: {
    u32 root_version_be;
    memcpy(&root_version_be, data + start + 4, sizeof(u32));
    u32 root_version = bswap(root_version_be);
    b8 is_big = root_version > 1000000;

    if (is_big) {
      hover.field_be<u32>("ROOT magic number")
      || hover.field<u32>("ROOT version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) {
        x = bswap(x);
        x -= 1000000;
        return push_str8_node_child(arena, prev, fmt, x);
      })
      || hover.field_be<u32>("fBEGIN: 0x%" PRIX64)
      || hover.field_be<u64>("fEND: 0x%" PRIX64)
      || hover.field_be<u64>("Seek Free: 0x%" PRIX64)
      || hover.field_be<u32>("NBytes Free: %u")
      || hover.field_be<u32>("N Free: %u")
      || hover.field_be<u32>("NBytes Name: %u")
      || hover.field_be<u8>("Units: %u")
      || hover.field_be<u32>("Compression: %u")
      || hover.field_be<u64>("Seek Info: 0x%" PRIX64)
      || hover.field_be<u32>("NBytes Info: %u")
      || hover.range("Padding", section.post_size)
      ;
    } else {
      hover.field_be<u32>("ROOT magic number")
      || hover.field_be<u32>("ROOT version: %u")
      || hover.field_be<u32>("fBEGIN: 0x%" PRIX64)
      || hover.field_be<u32>("fEND: 0x%" PRIX64)
      || hover.field_be<u32>("Seek Free: 0x%" PRIX64)
      || hover.field_be<u32>("NBytes Free: %u")
      || hover.field_be<u32>("N Free: %u")
      || hover.field_be<u32>("NBytes Name: %u")
      || hover.field_be<u8>("Units: %u")
      || hover.field_be<u32>("Compression: %u")
      || hover.field_be<u32>("Seek Info: 0x%" PRIX64)
      || hover.field_be<u32>("NBytes Info: %u")
      || hover.range("Padding", section.post_size)
      ;
    }
  } break;
  
  case Sec_TFile_Object: {
    if (!hover.tkey()) {
      b8 ok = hover.field_str8<u8>("File Name: %s")
            || hover.field_str8<u8>("File Title: %s")
            ;
      if (!ok) {
        u16 version_be;
        memcpy(&version_be, data + cur_field_off, sizeof(u16));
        u16 version = bswap(version_be);
        b8 is_big = version > 1000;

        if (is_big) {
          ok = ok || hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
             x = bswap(x);
             x -= 1000; 
             return push_str8_node_child(arena, prev, fmt, x);
          })
          || hover.field<u32>("Created: ", hover_display_datetime_str)
          || hover.field<u32>("Modified: ", hover_display_datetime_str)
          || hover.field_be<u32>("NBytes Key: %u")
          || hover.field_be<u32>("NBytes Name: %u")
          || hover.field_be<u64>("Seek Dir: 0x%" PRIX64) 
          || hover.field_be<u64>("Seek Parent: 0x%" PRIX64) 
          || hover.field_be<u64>("Seek Keys: 0x%" PRIX64) 
          || hover.field_be<u16>("UUID Vers.Class: %u")
          || hover.field_le<u16>("UUID: %u")
          ;
        } else {
          ok = ok || hover.field_be<u16>("Version: %u") 
          || hover.field<u32>("Created: ", hover_display_datetime_str)
          || hover.field<u32>("Modified: ", hover_display_datetime_str)
          || hover.field_be<u32>("NBytes Key: %u")
          || hover.field_be<u32>("NBytes Name: %u")
          || hover.field_be<u32>("Seek Dir: 0x%" PRIX64) 
          || hover.field_be<u32>("Seek Parent: 0x%" PRIX64) 
          || hover.field_be<u32>("Seek Keys: 0x%" PRIX64) 
          || hover.field_be<u16>("UUID Vers.Class: %u")
          || hover.field_le<u16>("UUID: %u")
          || hover.range("Padding", 3 * sizeof(u32))
          ;
        }
      }
    }
  } break;

  case Sec_RNTuple_Header: 
    if (!hover.tkey()) {
      b8 zipped;
      if (!hover.maybe_rootzip(&zipped)) {
        if (zipped) {
          hover.range("Payload", section.range.len - section.post_size)
          || hover.field_le<u64>("Checksum: 0x%" PRIX64)
          ;
        } else {
          hover.envelope_preamble()
          // NOTE: flags in principle require a more complex handling, but for now they are unused,
          // so they're always occupying only 8 bytes.
          || hover.field_le<u64>("Flags: 0x%" PRIX64)
          || hover.field_str8<u32>("Name: %s")
          || hover.field_str8<u32>("Description: %s")
          || hover.field_str8<u32>("ROOT version: %s")
          || hover.schema_description("Schema Description")
          || hover.field_le<u64>("Checksum: 0x%" PRIX64)
          ;
        }
      }
    }
    break;
  case Sec_RNTuple_Footer: {
    if (!hover.tkey()) {
      b8 zipped;
      if (!hover.maybe_rootzip(&zipped)) {
        if (zipped) {
          hover.range("Payload", section.range.len - section.post_size)
          || hover.field_le<u64>("Checksum: 0x%" PRIX64)
          ;
        } else {
          hover.envelope_preamble()
          // NOTE: flags in principle require a more complex handling, but for now they are unused,
          // so they're always occupying only 8 bytes.
          || hover.field_le<u64>("Flags: 0x%" PRIX64)
          || hover.field_le<u64>("Header checksum: 0x%" PRIX64)
          || hover.frame_header()
          || hover.schema_description("Schema Extension")
          //  - list of column group record frames (TODO)
          || hover.frame_header("Column Groups")
          //  - list of cluster group record frames (TODO)
          || hover.frame_header("Cluster Groups")
          || hover.range("Payload", section.range.len - hover.cur_field_off)
          || hover.field_le<u64>("Checksum: 0x%" PRIX64)
          ;
        }
      }
    }
  } break;
  case Sec_Page_List: {
    hover.tkey()
    || hover.maybe_rootzip()
    || hover.range("Payload", section.range.len - section.post_size) // TODO: improve
    || hover.field_le<u64>("Checksum: 0x%" PRIX64)
    ;
  } break;

  case Sec_Page: {
    // only try hovering a key if this is the first page of the cluster (<=> pre_size != 0)
    b8 ok = section.pre_size && hover.tkey();
    ok = ok || hover.maybe_rootzip()
    || hover.range("Payload", section.range.len - section.post_size) // TODO: improve
    || hover.field_le<u64>("Checksum: 0x%" PRIX64)
    ;
  } break;

  case Sec_TFile_Info: {
    hover.tkey()
    || hover.maybe_rootzip()
    // || hover.field<u32>("Byte Count: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u32 x) { 
    //    x = bswap(x);
    //    x -= 0x400000000; 
    //    return push_str8_node_child(arena, prev, fmt, x);
    // })
    // || hover.field_be<u16>("Version: %u")
    // || hover_try_object(hover)
    // || hover.field_be<u8>("Name: %u")
    // || hover.field_be<u32>("N Objects: %u")
    || hover.range("Payload", section.range.len) // TODO: improve
    ;
  } break;

  case Sec_TFile_FreeList: {
    if (!hover.tkey()) {
      u16 version_be;
      memcpy(&version_be, data + start + hover.cur_field_off, sizeof(u16));
      u32 version = bswap(version_be);
      b8 is_big = version > 1000;

      if (is_big) {
        hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
           x = bswap(x);
           x -= 1000; 
           return push_str8_node_child(arena, prev, fmt, x);
        })
        || hover.field_be<u64>("First: 0x%" PRIX64)
        || hover.field_be<u64>("Last: 0x%" PRIX64)
        ;
      } else {
        hover.field<u16>("Version: %u", [] (Arena *arena, String8_Node *prev, const char *fmt, u16 x) { 
           x = bswap(x);
           return push_str8_node_child(arena, prev, fmt, x);
        })
        || hover.field_be<u32>("First: 0x%X")
        || hover.field_be<u32>("Last: 0x%X")
        ;
      }
    }
  } break;

  default:;
  }

  return info;
}