sbool ai_html_wordThoughts_showMem = true;
static int ai_html_wordThoughts_searchMax = 100;
static int ai_html_wordThoughts_nodesMax_default = 1000;
static new ThreadLocal<Int> ai_html_wordThoughts_nodesMax;

static S ai_html_wordThoughts(S selectedWord) {
  ret ai_html_wordThoughts(ll(selectedWord));
}

static S ai_html_wordThoughts(L<S> selectedWords) {
  lock aiLock();
  int shrt = 80; // max length of entries shown
  Int oldShrt = setThreadLocal(ai_html_shortenTextForDisplay_length, shrt);
  try {
    S t = ai_html_triangle();
    S selectedWord = first(selectedWords);
    S title = h3(htmlencode(selectedWord));
    
    new Matches m;
    if (swic_trim(selectedWord, "[search]", m)) {
      int max = ai_html_wordThoughts_searchMax; 
      L<S> l = indexedTerms_scoredSearch(m.rest(), max+1);
      ret title
        + p(l(l) > ai_html_wordThoughts_searchMax ? max + "+ search results" : n(l, "search result"))
        + ul(map html_encyclopediaTopic(takeFirst(max, l)));
    }
    
    new L<WebNode> nodes;
    int max = or(ai_html_wordThoughts_nodesMax!, ai_html_wordThoughts_nodesMax_default);
    Set<S> ignoredWebs = ai_ignoredWebs();
    for (S word : selectedWords) {
      nodes.addAll(web_nodesNotFromCertainWebs(ignoredWebs, indexedNodes(word)));
      if (l(nodes) >= max) break;
    }
    int realN = l(nodes);
    truncateList(nodes, max);
      
    L<WebRelation> rel1 = web_dropConfirmedPossibles(web_collectBackwardRelations(nodes));
    L<WebRelation> rel2 = web_dropConfirmedPossibles(web_collectForwardRelations(nodes));
  
    final MultiMap<S> mm1 = orderedMultiMap(); // HTML -> web id
    final MultiMap<S> mm2 = orderedMultiMap();
    final MultiMap<S> mm3 = orderedMultiMap();

    for (bool invalid : ll(false, true)) {
      for (WebRelation r : ai_sortRelationsByImportance(ai_filterInvalid(invalid, rel2)))
        mm1.put(t + " " + ai_textHTML(r) + t + ai_renderNodeHTML(r.b), r.web.globalID());
        
      for (WebRelation r : ai_sortRelationsByImportance(ai_filterInvalid(invalid, rel1)))
        mm2.put(ai_renderNodeHTML(r.a) + t + ai_textHTML(r) + t + ai_textHTML(r.b), r.web.globalID());
      
      if (!invalid)
        for (WebNode n : nodes)
          if (n instanceof WebRelation)
            mm3.put(ai_renderNodeHTML(n/WebRelation.a) + t + ai_textHTML(n) + t + ai_textHTML(n/WebRelation.b), n.web.globalID());
    }
        
    // l1 is for web stuff
    L<S> l1 = ll();
    Web web = ai_getWebFromTerm(selectedWord);
    if (web != null) {
      S link = ai_html_webLink(web.globalID());
      bool inv = ai_isInvalidWeb(web);
      l1.add(inv ? "is an " + ahref(link, "invalid web") : "is a " + ahref(link, "web"));
    }
    
    new L<S> lists;
    if (nempty(l1)) lists.add(ul(l1));
    for (MultiMap<S> mm : ll(m1, m2, m3))
      lists.add(ul(map html_linkURLs(
        allToString(
          map(multiMapKeysByPopularity(mm), func(S s) {
            L<S> webIDs = mm.get(s);
            ret s + " [" + joinWithComma(map ai_html_linkedWebWithThumbs(webIDs)) + "]";
          })
      ))));
    
    //long nVirtual = ai_numberOfVirtualWebs();
    long mem = ai_html_wordThoughts_showMem ? memoryUsedAfterLastGC() : 0;
    ret title
      + (realN > max ? p("List truncated (" +  n(realN, "total node") + ")") : "")
      + lines(lists)
      + p(n(nodes, "node") + " out of " + ahref(smartBotOfficialURL(), nWebs(ai_allWebsCount())) + (loading() ? " (LOADING)": "")
        //+ (nVirtual == 0 ? "" : " + " + formatWithThousandsSeparator(nVirtual) + " virtual")
        + (mem == 0 ? "" : ". " + toMB(mem) + " MB used"));
  } finally {
    ai_html_shortenTextForDisplay_length.set(oldShrt);
  }
}