"use strict";

$(document).ready(function () {

  // ----------------------------------
  // Global values and objects to be initialized as soon as the page loads
  // ----------------------------------

  let index_of_tab_button_focus = get_index_of_active_tab_button();
  // List of senses, and map of corresponding items in the table-of-contents,
  //  used to manage highlighting of items in the table-of-contents.
  const sense_elements = get_sense_elements(),
    table_of_contents_mappings = map_toc_link_elements_to_target_sense_ids();
  // We keep track of the ID of the sense that's currently in (or near the top of)
  //  the viewport, so we can update table-of-contents highlight when it changes.
  let id_of_sense_in_viewport = null;


  // Supporting functions to initialize these values

  function get_tab_buttons() {
    return $("button.tab-bar-item");
  }


  function get_index_of_active_tab_button() {
    // Index position (0-6) of the tab button (within the set of
    //  tab buttons) which is currently set as active
    //  (i.e. has the class 'tab-bar-item-active').
    const active_class = "tab-bar-item-active",
      tab_buttons = get_tab_buttons();
    let active_index = 0; // default
    tab_buttons.each(function (index, button) {
      if ($(button).hasClass(active_class)) {
        active_index = index;
      }
    });
    return active_index;
  }


  function map_toc_link_elements_to_target_sense_ids() {
    // Create a map in which each key is the ID of a sense,
    //  and the corresponding value is the <a> element in the
    //  table-of-contents that links to that sense. We use this
    //  to highlight the link in the table-of-contents when the
    //  corresponding sense is in the viewport.
    const table_of_contents_containers = $("div.table-of-contents-wrapper");
    const table_of_contents_mappings = {};
    table_of_contents_containers.each(function () {
      const table_of_contents_container = $(this);
      table_of_contents_container.find("li.toc-item > a").each(function () {
        const link_element = $(this),
          target_id = String(link_element.data("target-element"));
        table_of_contents_mappings[target_id] = link_element;
      });
    });
    return table_of_contents_mappings;
  }


  function get_sense_elements() {
    // Create a list of all the senses and sense-like elements
    //  We use this to check which sense is in the viewport, so that we
    //  can highlight the corresponding link in the table-of-contents.

    //  Regular senses as found in 'meaning & use', 'phrasal verbs', and 'compounds'
    //   (li.sense and li.compound-group elements). These are always leaf nodes
    //   in the sense-structure tree, which is what we want
    const regular_senses = $("li.sense, li.compound-group");

    // Sections in a variant-forms list (div.variant-forms-subsection...)
    //  This is a  bit more complicated, because these may or may not be leaf nodes
    //  in the variant-forms tree. Since we only want leaf nodes, we have to filter
    //  out instances of these elements which contain other instances as child elements.
    const variant_forms_selector = "div.variant-forms-subsection-v1, div.variant-forms-subsection-v1sub, div.variant-forms-subsection-v2";
    const variant_forms_sections = $(variant_forms_selector).filter( function() {
      if ($(this).find(variant_forms_selector).length > 0) { return false; }
      else { return true; }
    });
    return regular_senses.add(variant_forms_sections);
  }




  // ----------------------------------
  // Show dictionary entry in tabbed or non-tabbed view, depending on personal settings or cookies
  // ----------------------------------

  (function() {
    if ($("div#tabbed-view-switch").length > 0) {
      const cookie = Cookies.get("tabbed");
      if (cookie && cookie === "true") {
          show_tabbed_view();
      } else if (cookie && cookie === "false") {
          show_nontabbed_view();
      } else if (window.hasOwnProperty("defaultTabbedView")) {
        if (defaultTabbedView) {
          show_tabbed_view();
        } else {
          show_nontabbed_view();
        }
      } else {
        // default to tabbed view
        show_tabbed_view();
      }
    }
  })();




  // ----------------------------------
  // Tab switching
  // ----------------------------------

  // On initial page load, set the tab position of all tabs to zero by default.
  //  This may later be updated when the user switches tabs, if the current tab
  //  has been scrolled to a different position - see save_tab_position() function
  //  below.
  $("button.tab-bar-item").attr("scrollPos", "0");


  function get_current_tabbed_mode() {
    // Is the entry currently displayed in tabbed or non-tabbed mode?
    if ($("div#tabbed-view-switch").length > 0) {
      if ($("button#tabbed-view-switch-nontabbed").hasClass("tabbed-view-switch-active")) {
        return "nontabbed";
      } else {
        return "tabbed";
      }
    }
    // In the absence of a tab-switch button (e.g. if the user is not logged in),
    // we default to assuming that the entry is in tabbed mode
    return "tabbed";
  }


  function get_active_button() {
    const tab_buttons = get_tab_buttons(),
      active_index = get_index_of_active_tab_button();
    return $(tab_buttons.get(active_index));
  }


  function get_identifier_of_active_tab() {
    // Find the nav-bar button that selects the currently active tab
    const active_button = get_active_button(),
      tab_identifier = active_button.data("target-tab");
    return tab_identifier || "meaning_and_use";
  }


  $(document).on("click", "button.tab-bar-item", function () {
    // When the user clicks a tab-bar button, switch to the corresponding tab

    // N.b. we have to do
    //    $(document).on("click", "button.tab-bar-item", function () {...})
    //  because buttons may get added dynamically after page-load
    //  (e.g. when the tab-bar is copied into the mobile-browse modal); these
    //  dynamically-added buttons would be missed by the more usual
    //    $("button.tab-bar-item").click(function () {...})

    // In case the tab-button is being clicked from within the mobile-browse modal,
    //  close the modal
    const modal = $(this).closest("div.modal");
    if (modal.length > 0 && modal.attr("id") === "browse-modal") {
      close_modal($(this));
    }

    // Record the scroll position of the currently active tab (so that we can scroll back this
    //  position next time this tab is displayed).
    save_tab_position();

    // Switch highlight to the newly-selected tab button, and display the newly-selected tab
    const tab_identifier = $(this).data("target-tab");
    set_tab_bar_active_status(tab_identifier);
    set_tab_visibility(tab_identifier);

    // Update the global-scope "index_of_tab_button_focus" variable
    index_of_tab_button_focus = get_index_of_active_tab_button();

    // Change the page URL by adding a "?tab=[tab-id]" fragment, e.g. "?tab=meaning_and_use"
    const url = new URL(window.location);
    url.searchParams.set("tab", tab_identifier);
    window.history.replaceState({}, "", url);

    // Scroll the page to whatever position was recorded for this tab the last time
    //  it was displayed
    const target_tab_position = $(this).attr("scrollPos");
    get_tab_buttons().removeClass("current");
    $("button.tab-bar-item[data-target-tab=" + tab_identifier + "]").addClass("current");
    $(window).scrollTop(target_tab_position);
  });


  function save_tab_position() {
    // Record the scroll position of the currently active, by storing the position
    //  as the value of the tab button's "scrollPos" attribute
    const header_height = $("header.siteHeader").outerHeight(),
      entry_head_height = $(".entry-head").outerHeight(),
      tab_buttons_bar_top = header_height + entry_head_height;

    // Set current scroll pos on current tab
    const current_tab_position = $(window).scrollTop();

    $(".tab-bar-item.current").attr("scrollPos", current_tab_position);

    $(".tab-bar-item").each(function () {
      // If bar is currently fixed and target tab scroll position is less than
      //  the fixing point, then set scroll target to fixing point
      if ($(".tab-bar.fixed")[0] && $(this).attr("scrollPos") < tab_buttons_bar_top) {
        $(this).attr("scrollPos", tab_buttons_bar_top + 1);
      }
      // If bar is not currently fixed and target tab scroll position is less
      //  than the fixing point, then set target to current scroll position
      if (!$(".tab-bar").hasClass("fixed") && $(this).attr("scrollPos") < tab_buttons_bar_top) {
        $(this).attr("scrollPos", current_tab_position);
      }
    });
  }


  function set_tab_bar_active_status(active_tab_identifier) {
    // Highlight the button for the currently selected tab (by giving it
    //  the class 'tab-bar-item-active')
    const active_class = "tab-bar-item-active",
      tab_buttons = get_tab_buttons();
    // Make all tab links inactive...
    tab_buttons.removeClass(active_class).attr("aria-selected", "false").attr("tabindex", "-1");
    // ...then make the selected tab link active again
    tab_buttons.each(function () {
      const tab_identifier = $(this).data("target-tab");
      if (tab_identifier === active_tab_identifier) {
        $(this).addClass(active_class).attr("aria-selected", "true").attr("tabindex", "0");
      }
    });
  }


  function set_tab_visibility(tab_identifier) {
    // Make the contents of the selected tab visible, while hiding all other tabs
    //   hide all tab contents...
    $("section.tab-section").addClass("hidden");
    //   ...then show the content of the selected tab
    $(`section#${tab_identifier}`).removeClass("hidden");
  }




  // -----------------------------------
  // Change tab-button focus using left- and right-arrow keys
  // -----------------------------------

  $("div#entry-content-tab-bar div.tab-buttons").keydown(function (event) {
    const active_class = "tab-bar-item-active",
      tab_buttons = get_tab_buttons();

    const change_tab_button_focus = function (index_of_selected_button) {
      const button = $(tab_buttons.get(index_of_selected_button));
      button.focus();
    };

    if (event.which === 39) {
      // right arrow key
      index_of_tab_button_focus += 1;
      if (index_of_tab_button_focus >= tab_buttons.length) {
        index_of_tab_button_focus = 0;
      }
      change_tab_button_focus(index_of_tab_button_focus);
    } else if (event.which === 37) {
      // left arrow key
      index_of_tab_button_focus -= 1;
      if (index_of_tab_button_focus < 0) {
        index_of_tab_button_focus = tab_buttons.length - 1;
      }
      change_tab_button_focus(index_of_tab_button_focus);
    }
  });




  // ---------------------------------------------
  // Switch between tabbed and nontabbed view
  // ---------------------------------------------

  $("button#tabbed-view-switch-nontabbed").click( function () {
    show_nontabbed_view();
    // Log that the user has turned off tabbed view
    const newNetInsight = cleanNetInsightDto;
    const newData  = {event_type: "full-text"};
    Object.assign(newNetInsight, newData);
    var query = Object.keys(newNetInsight).map(key => key + "=" + encodeURIComponent(newNetInsight[key])).join("&");
    try {
      ntptEventTag(query);
    } catch (e) {
      console.log("Could not query ntptEventTag");
    }
  });

  $("button#tabbed-view-switch-tabbed").click( function () {
    show_tabbed_view();
  });



  function show_tabbed_view() {
    // Update the appearance of the switch
    $("button#tabbed-view-switch-nontabbed").removeClass("tabbed-view-switch-active").attr("aria-checked", "false");
    $("button#tabbed-view-switch-tabbed").addClass("tabbed-view-switch-active").attr("aria-checked", "true");

    const tab_identifier = get_identifier_of_active_tab();
    set_tab_bar_active_status(tab_identifier);
    index_of_tab_button_focus = get_index_of_active_tab_button();
    set_tabbed_view_settings("tabbed");

    // Show the tab-bar
    $("div#entry-content-tab-bar").removeClass("hidden");
    // Hide all tab contents...
    $("section.tab-section").addClass("hidden");
    $("div.tab-header").addClass("invisible");
    // ...then show the content of the selected tab
    $(`section#${tab_identifier}`).removeClass("hidden");
    // Unhide all paywall banners
    $("div.paywallWrapper").removeClass("hidden");
  }


  function show_nontabbed_view() {
    // Update the appearance of the switch
    $("button#tabbed-view-switch-nontabbed").addClass("tabbed-view-switch-active").attr("aria-checked", "true");;
    $("button#tabbed-view-switch-tabbed").removeClass("tabbed-view-switch-active").attr("aria-checked", "false");;

    set_tabbed_view_settings("nontabbed");

    // Hide the tab-bar
    $("div#entry-content-tab-bar").addClass("hidden");
    // Show all tab contents...
    $("section.tab-section").removeClass("hidden");
    $("div.tab-header").removeClass("invisible");
    // ...but hide the factsheet
    $("section#factsheet").addClass("hidden");
    // Hide all paywall banners, except the first one
    $("div.paywallWrapper").addClass("hidden");
    $("div.paywallWrapper").first().removeClass("hidden");
  }


  function set_tabbed_view_settings(view_mode) {
    // view_mode is "tabbed" or "nontabbed"
    set_tabbed_view_cookie(view_mode);
    // Swap out sidebar, hide or show full etymology, hide or show full frequency section
    toggle_sidebars(view_mode);
    toggle_etymology(view_mode);
    toggle_frequency(view_mode);
    // Set appropriate class on the top-level <article> element; this is used
    //  in some CSS selectors to control entry layout
    $("article").removeClass("tabbed").removeClass("nontabbed").addClass(view_mode);
  }


  function set_tabbed_view_cookie(view_mode) {
    // view_mode is "tabbed" or "nontabbed"
    let new_cookie_value;
    if (view_mode === "tabbed") {
      new_cookie_value = "true";
    } else {
      new_cookie_value = "false";
    }
    const current_cookie_value = Cookies.get("tabbed");
    if (current_cookie_value === null || current_cookie_value != new_cookie_value) {
      Cookies.set("tabbed", new_cookie_value, {expires: 0.5});
    }
  }


  function toggle_sidebars(view_mode) {
    // View_mode is "tabbed" or "nontabbed"
    if (view_mode === "tabbed") {
      // When we switch from non-tabbed to tabbed view, we hide
      //  the entry-level sidebar, and show sidebars for individual tabs.
      $("div#entry_content_sidebar").addClass("hidden");
      move_sidebar_contents("entry", "tabs");
      $("div.tab-content-sidebar").removeClass("hidden");
      // Unhide all "Hide all quotations" controls in tab sidebars
      $("fieldset.quotation-controls").removeClass("hidden");
      $("fieldset.in-other-dictionaries").removeClass("hidden");
      $("fieldset.earlier-versions").removeClass("hidden");
    } else {
      // When we switch from tabbed to non-tabbed view, we hide
      //  sidebars for individual tabs, and show the entry-level sidebar.
      $("div.tab-content-sidebar").addClass("hidden");
      move_sidebar_contents("tabs", "entry");
      $("div#entry_content_sidebar").removeClass("hidden");
      // Hide all "Hide all quotations" controls in tab sidebars...
      $("fieldset.quotation-controls").addClass("hidden");
      $("fieldset.in-other-dictionaries").addClass("hidden");
      $("fieldset.earlier-versions").addClass("hidden");
      // ...but unhide the "Hide all quotations" control in the entry-level sidebar
      $("fieldset#quotation_controls_nontabbed").removeClass("hidden");
      $("fieldset#in_other_dictionaries_nontabbed").removeClass("hidden");
      $("fieldset#earlier_versions_nontabbed").removeClass("hidden");
    }
  }


  function move_sidebar_contents(source, target) {
    // When we switch from tabbed to non-tabbed view, we move
    //  sidebar contents from sidebars for individual tabs to the entry-level sidebar.
    // Conversely, when we switch from non-tabbed to tabbed view, we move
    //  sidebar contents from the entry-level sidebar to sidebars for individual tabs.
    const entry_level_sidebar = $("div#entry_content_sidebar");

    // Go through each tab in turn, looking for ones that have a sidebar
    $("section.tab-section").each(function () {
      const tab_identifier = $(this).attr("id");
      const tab_level_container = $(this).find("div.tab-content-sidebar").first();
      const entry_level_container = entry_level_sidebar.find(`li#${tab_identifier}_toc_item > div.nested-sidebar`).first();

      if (tab_level_container.length > 0 && entry_level_container.length > 0) {
        // Decide which direction we're moving the sidebar contents:
        //  from the tab-level sidebar to the entry-level sidebar,
        //  or from the entry-level sidebar to the tab-level sidebar.
        let source_container, target_container;
        if (source === "tabs") {
          source_container = tab_level_container;
          target_container = entry_level_container;
        } else {
          source_container = entry_level_container;
          target_container = tab_level_container;
        }

        // Move the sidebar contents from its tab-level sidebar to the entry-level sidebar,
        //  or vice versa
        if (source_container.find("div.sidebar-contents-container").length > 0) {
          target_container.html(""); // make sure the target container is empty to start
          source_container.find("div.sidebar-contents-container").each(function () {
            target_container.append($(this)); // move from the source container to the target
          });
        }
      }
    });
  }


  function toggle_etymology(view_mode) {
    const etymology_section = $("section#etymology"); 
    // View_mode is "tabbed" or "nontabbed"
    if (view_mode === "tabbed") {
      etymology_section.find("div#main_etymology_complete").removeClass("hidden");
      etymology_section.find("div#main_etymology_truncated").addClass("hidden");
      etymology_section.find("button#main_etymology_show_less").addClass("hidden");
    }
    if (view_mode === "nontabbed") {
      if (etymology_section.find("div#main_etymology_truncated").length) {
        etymology_section.find("div#main_etymology_complete").addClass("hidden");
        etymology_section.find("div#main_etymology_truncated").removeClass("hidden");
        etymology_section.find("button#main_etymology_show_less").removeClass("hidden");
      }
    }
  }


  function toggle_frequency(view_mode) {
    const frequency_section = $("section#frequency"); 
    // View_mode is "tabbed" or "nontabbed"
    if (view_mode === "tabbed") {
      frequency_section.find("div#frequency_series").removeClass("hidden");
      frequency_section.find("div#frequency_controls").addClass("hidden");
    }
    if (view_mode === "nontabbed") {
      frequency_section.find("div#frequency_series").addClass("hidden");
      frequency_section.find("div#frequency_controls").removeClass("hidden");
      frequency_section.find("button#frequency_series_show_more").removeClass("hidden");
      frequency_section.find("button#frequency_series_show_less").addClass("hidden");
    }
  }



  // ----------------------------------
  // Modals
  // ----------------------------------

  $(document).on("click", "button.modal-trigger", function () {
    const modalTriggerButton = this;
    // Open a modal
    //
    // N.b. we have to do
    //   $(document).on("click", "button.modal-trigger", function () {...})
    // because buttons may get added dynamically after page-load (esp. in the
    // thesaurus page, where content is generated by thymeleaf); these
    // dynamically-added buttons would be missed by the more usual
    //   $("button.modal-trigger").click(function () {...})

    // In case we're already in a modal (e.g. in the mobile-browse modal);
    //  we close the current Modal befoire opening a new one.
    close_modal($(this));

    const modal_id = $(this).data("trigger-for");
    $(this).attr("aria-expanded", "true");
    $(`#${modal_id}`).removeClass("hidden").addClass("shown");
    
    $("html").css("overflow-y", "hidden");
    if (modal_id === "quotation-source-details-modal") {
      const record_id = $(this).data("quotation-source-details-id");
      const quotation = $(this).closest("li").clone();
      populate_quotation_source_details_modal(modal_id, quotation, record_id, jQuery.fn.modalFocusables, modalTriggerButton);
    } else if (modal_id === "factsheet-modal") {
      populate_factsheet_modal(modal_id, jQuery.fn.modalFocusables, modalTriggerButton);
    } else if (modal_id === "cite-modal") {
      populate_cite_modal($(this), modal_id, jQuery.fn.modalFocusables, modalTriggerButton);
    } else if (modal_id === "browse-modal") {
      populate_mobile_browse_modal(jQuery.fn.modalFocusables, modal_id, modalTriggerButton);
    } else {
      jQuery.fn.modalFocusables(modal_id, modalTriggerButton);
    }
  });


  $(document).on("click", "button.modal-close", function () {
    // Close a modal
    //
    // N.b. we have to do
    //   $(document).on("click", "button.modal-close", function () {...})
    // because buttons may get added dynamically after page-load (esp. in the
    // thesaurus page, where content is generated by thymeleaf); these
    // dynamically-added buttons would be missed by the more usual
    //   $("button.modal-close").click(function () {...})

    // Find the container modal element that contains the 'close' button
    //  that has just been clicked
    close_modal($(this));
  });


  function close_modal(clicked_element) {
    // Close a modal
    const modal = clicked_element.closest("div.modal");
    if (modal.length > 0) {
      const modal_id = modal.attr("id");
      // Hide the modal
      modal.addClass("hidden").removeClass("shown");
      $("html").css("overflow-y", "visible");
      // Find the button(s) that controls this modal, and change its
      // @aria-expanded state to "false"
      $("button.modal-trigger").each(function () {
        const target_id = $(this).data("trigger-for");
        if (target_id === modal_id) {
          $(this).attr("aria-expanded", "false");
        }
      });
      if (modal_id === "browse-modal") {
        move_sidebar_contents_to_or_from_mobile_browse_modal("modal", "entry", jQuery.fn.modalFocusables, modal_id, null);
      }
    }
  }




  // ----------------------------------
  // Factsheet modal (used when the entry page is in non-tabbed mode)
  // ----------------------------------

  function populate_factsheet_modal(modal_id, callback, modalTriggerButton) {
    // Populate the factsheet modal by copying the content from the factsheet tab
    const modal = $(`#${modal_id}`);
    const modal_body = modal.find("div.modal-body");
    if (!modal_body.html()) {
      const factsheet_clone = $("section.factsheet").clone();
      modal_body.append(factsheet_clone);
      create_new_sparkline(factsheet_clone);
    }
    callback(modal_id, modalTriggerButton);

    function create_new_sparkline(factsheet) {
      // Create a new version of the sparkline; it doesn't show up if
      // you just copy the original sparkline from the factsheet tab
      const old_sparkline_id = frequency_chart_config.sparkline_canvas_id,
        new_sparkline_id = old_sparkline_id + "2",
        new_canvas = `<canvas id=\"${new_sparkline_id}\" aria-hidden=\"true\"/>`;
      factsheet.find(`canvas#${old_sparkline_id}`).replaceWith(new_canvas);
      create_sparkline(new_sparkline_id);
    }
  }




  // ----------------------------------
  // 'Cite' modal
  // ----------------------------------

  function populate_cite_modal(button, modal_id, callback, modalTriggerButton) {
    // Populate the "cite" modal with information stored as data attributes
    // on the button used to open the modal. (These buttons appear at the
    // top of the entry, and at each sense within the entry; so the content
    // of the modal will depend on which button is used to open it.)
    const modal = $(`#${modal_id}`),
      citations_container = modal.find("div.modal-body div.citations"),
      reference_default = button.data("reference"),
      reference_chicago = italicize_parts_of_speech(button.data("reference-chicago")),
      reference_mla9 = italicize_parts_of_speech(button.data("reference-mla9")),
      version_date = button.data("version-date"),
      doi = button.data("doi");

    // The MLA9 reference should have a full stop at the end. But it may *already* have
    //  a full stop (if it ends with a part-of-speech, like 'n.'). So we check first,
    //  and append a full stop only if required.
    let reference_mla9_punctuated = reference_mla9;
    if (! reference_mla9_punctuated.endsWith(".") && ! reference_mla9_punctuated.endsWith(".</i>")) {
      reference_mla9_punctuated += ".";
    }

    // Fill in the empty gaps in the 'Cite' modal. We use .html() rather than .text()
    //  when filling in the gaps for the Chicago and MLA9 entry reference, since we've italicized
    //  these (above); so they now contain HTML <i>...</i> tags. 
    citations_container.find("span.citation-reference-chicago").html(reference_chicago);
    citations_container.find("span.citation-reference-mla9").html(reference_mla9_punctuated);
    citations_container.find("span.citation-version-date").text(version_date);
    citations_container.find("span.citation-doi").text(doi);
    modal.find("span.citation-reference").text(reference_default);

    callback(modal_id, modalTriggerButton);
  }


  function italicize_parts_of_speech(reference) {
    // Italicize the part(s)-of-speech within an entry-reference string, as required
    //  in Chicago and MLA9 citation styles. E.g.
    //    'Mammoth, N. & Adj.' -> 'mammoth, <i>N.</i> & <i>Adj.</i>' (MLA9 style)
    //    'mammoth (n. & adj.)' -> 'mammoth (<i>n.</i> & <i>adj.</i>)' (Chicago style)
    //    'Georgian, Adj.(3) & N.(3)' -> 'Georgian, <i>Adj.</i>(3) & <i>N.</i>(3)'  (MLA9 style)
    //    'Georgian (adj.3 & n.3)' -> 'Georgian (<i>adj.3</i> & <i>n.3</i>)'  (Chicago style)
    //
    // The patterns used could *very occasionally* match somthing that's not a part-of-speech,
    //  e.g. something in an unusual headword (an initialism or abbreviation?). But this is low risk.  
    const regex1 = /([ (])((n|adj|v|adv|conj|int|pron|prep|N|Adj|V|Adv|Conj|Int|Pron|Prep)\.[0-9]*)/g,
      regex2 = /([ (])((prefix|suffix|comb\. form|Prefix|Suffix|Comb\. form)[0-9]*)/g;
    return reference
      .replace(regex1, "$1<i>$2</i>")
      .replace(regex2, "$1<i>$2</i>")
      .replace("sense <i>V.</i>", "sense V.");
  }


  $(document).on("click", "button.copy-citation-button", function () {
    // Copy the entry- or sense-level citation to the user's clipboard
    //
    // N.b. we have to do
    //   $(document).on("click", "button.copy-citation-button", function () {...})
    // because buttons may get added dynamically after page-load (esp. in the
    // sense-level cite modal); these dynamically-added buttons would be missed
    // by the more usual
    //   $("button.copy-citation-button").click(function () {...})
    const citation_line = $(this).parent().siblings("div").first();
    const citation_text = citation_line.text();
    const citation_text_clean = citation_text.replace(/\n|\t/g, " ").replace(/  +/g, " ").trim();
    navigator.clipboard.writeText(citation_text_clean);

    // Briefly display the 'copied' message
    const confirmation_message = $(this).siblings(".citation-copied-message");
    confirmation_message.show();
    confirmation_message.delay(3000).fadeOut("slow");
  });




  // ----------------------------------
  // Mobile browse modal
  // ----------------------------------

  function populate_mobile_browse_modal(callback, modal_id, modalTriggerButton) {
    // Populate the mobile-browse modal with the tab bar
    //  and the entry sidebar
    const tabs_container = $("div#mobile-browse-modal-tabs");
    // Make sure that the modal body is empty
    tabs_container.empty();

    if (get_current_tabbed_mode() === "tabbed") {
      const tab_bar = $("div#entry-content-tab-bar").clone();
      tabs_container.append(tab_bar);
    }
    move_sidebar_contents_to_or_from_mobile_browse_modal("entry", "modal", callback, modal_id, modalTriggerButton);
  }


  function move_sidebar_contents_to_or_from_mobile_browse_modal(source, target, callback, modal_id, modalTriggerButton) {
    const modal = $("div#browse-modal"),
      modal_sidebar_container = modal.find("div#mobile-browse-modal-sidebar");

    let main_sidebar_container;
    if (get_current_tabbed_mode() === "tabbed") {
      // If we are currently in *tabbed* mode, we get the sidebar for the active tab
      const active_tab_identifier = get_identifier_of_active_tab(),
        active_tab_section = $(`section#${active_tab_identifier}`);
      main_sidebar_container = active_tab_section.find("div.tab-content-sidebar");
    } else {
      // If we are currently in *non-tabbed* mode, we get the sidebar for the entry as a whole
      main_sidebar_container = $("div#entry_content_sidebar"); 
    }

    if (main_sidebar_container.length > 0) {
      // If there *is* a sidebar for the current tab:
      // When we open the mobile-browse modal, we move
      //  sidebar contents from the current-tab sidebar into the mobile-browse modal.
      // Conversely, when we close the mobile-browse modal, we move
      //  sidebar contents from mobile-browse modal to the current-tab sidebar.
      let source_container, target_container;
      if (target === "modal") {
        source_container = main_sidebar_container;
        target_container = modal_sidebar_container;
      } else {
        source_container = modal_sidebar_container;
        target_container = main_sidebar_container;
      }

      target_container.empty(); // make sure the target container is empty to start
      let swappable_elements;
      if (get_current_tabbed_mode() === "tabbed") {
        swappable_elements = source_container.find("div.sidebar-contents-container");
      } else {
        swappable_elements = source_container.children(); 
      }
      swappable_elements.each(function () {
        target_container.append($(this)); // move from the source container to the target
      });
    } else {
      // If there is no sidebar for the current tab, and therefore nothing to move in/out
      // of the mobile-browse modal, all we do is make sure that the corresponding section
      // of the mobile-browse modal is empty. 
      modal_sidebar_container.empty();
    }
    
    callback(modal_id, modalTriggerButton);
  }


  $(document).on("click", "div#browse-modal nav.table-of-contents a", function (event) {
    // Go the right target section when one of the links in the mobile-browse modal's
    //  table-of-contents is clicked.
    //
    // N.b. we have to do
    //   $(document).on("click", "... a", function () {...})
    // because these links get added dynamically after page-load; these
    // dynamically added links would be missed by the more usual
    //   $("... a").click(function () {...})
    event.preventDefault();
    const target_element_id = $(this).data("target-element");
    close_modal($(this));
    $(`#${target_element_id}`).get(0).scrollIntoView();
  });




  // ----------------------------------
  // Quotation source-details modal
  // ----------------------------------

  const BIBLIOGRAPHIC_RECORD_ENDPOINT = "/citations/",
    ODNB_URL_TEMPLATE = "https://doi.org/10.1093/ref:odnb/",
    ANB_URL_TEMPLATE = "https://dx.doi.org/10.1093/anb/9780198606697.article.",
    QUOTATION_QUERY_TEMPLATE = "/search/advanced/Quotations?quotDateFirstUse=all&page=1&sortOption=AZ&";


  function populate_quotation_source_details_modal(modal_id, quotation, record_id, callback, modalTriggerButton) {
    // Populate the modal used to show bibliographical/biographical
    // details about the source of a quotation. The model is opened
    // by clicking the button to the right of each quotation's citation.
    // The content will be different for each quotation.
    const modal = $(`#${modal_id}`);
    // Remove any prior content from the modal
    evacuate_quotation_source_details_modal(modal);
    // Repopulate modal with new content
    // Copy in the quotation
    populate_source_details_modal_with_quotation(modal, quotation);
    // Add in bibliographic details taken from the bibliographic record for this citation
    $.get(BIBLIOGRAPHIC_RECORD_ENDPOINT + record_id, function (bibliographic_record) {
      if (bibliographic_record.id) {
        populate_source_details_modal_with_bibliographic_record(modal, bibliographic_record, quotation);
      } else {
        populate_source_details_modal_with_null_content(modal, record_id);
      }
      callback(modal_id, modalTriggerButton);
    }).fail(function () {
      populate_source_details_modal_with_null_content(modal, record_id);
      callback(modal_id, modalTriggerButton);
    });
  }


  function evacuate_quotation_source_details_modal(modal) {
    modal.find("ol.quotation-container").empty();
    modal.find("div#quotation-source-details-bibliographic").empty();
    modal.find("div#quotation-source-details-components").empty();
  }


  function populate_source_details_modal_with_quotation(modal, quotation) {
    quotation.find("button").remove();
    quotation.find("span.quotation-separator").remove();
    modal.find("ol.quotation-container").append(quotation);
  }


  function populate_source_details_modal_with_bibliographic_record(modal, bibliographic_record, quotation) {
    modal.find("div#quotation-source-details-bibliographic").html(
      compose_html_for_source_details_reference(bibliographic_record, quotation)
    );
    modal.find("div#quotation-source-details-components").html(
      compose_html_for_source_details_components(bibliographic_record)
    );
  }


  function compose_html_for_source_details_reference(bibliographic_record, quotation) {
    let html = "";
    if (bibliographic_record.key_details) {
      const details = bibliographic_record.key_details;
      html += '<h3 class="bibliographic-details">';
      if (details.author) {
        html += `${details.author} &#x2022; `;
      }
      if (details.title) {
        html += `<i>${details.title}</i>`;
      }
      if (details.edition) {
        html += ` &#x2022; ${details.edition}`;
      }
      if (details.volumes) {
        html += ` &#x2022; ${details.volumes}`;
      }
      html += "</h3>";
      if (details.publisher) {
        html += `<div>${details.publisher}</div>`;
      }
      if (details.publication_details) {
        html += `<div>${details.publication_details}</div>`;
      }
      if (details.catalogue_reference) {
        html += `<div>${details.catalogue_reference}</div>`;
      }
      if (details.publishable_notes) {
        html += `<div>${details.publishable_notes}</div>`;
      }
    }
    if (bibliographic_record.link_to_full_text) {
      html += compose_full_text_link(
        bibliographic_record.link_to_full_text,
        bibliographic_record.full_text_working_pagination,
        quotation
      )
    }
    return html;
  }


  function compose_full_text_link(url, working_pagination, quotation) {
    const page_number = get_page_number_from_quotation(quotation);
    if (page_number && working_pagination) {
      const url_affix = `urlappend=;num=${page_number};u=1`,
        connector = url.includes("?") ? "&" : "?";
      url = url + connector + url_affix;
    };
    return `<h4>Full text:</h4><ul><li><a href="${url}">HathiTrust</a></li></ul>`;
  }


  function get_page_number_from_quotation(quotation) {
    const quotation_text = quotation.text(),
      tokens = quotation_text.split(" "),
      // page number should be the last token in the quotation
      last_token_in_quotation = tokens[tokens.length - 1],
      // remove column, if any (e.g. "241/2" becomes "241")
      page_number = last_token_in_quotation.split("/")[0];
    // Check that the last token is an integer
    if ("" + parseInt(page_number) != page_number) {
      return null;
    }
    // Check if this is a line number - i.e. second-to-last token is "l."
    const penultimate_token_in_quotation = tokens[tokens.length - 2];
    if (penultimate_token_in_quotation === "l.") {
      return null;
    }
    // Page number over 1000 is unlikely to be a real page number - could
    //  be a line number missing a preceding "l."?
    if (parseInt(page_number) > 1000) {
      return null;
    }
    return page_number;
  }


  function compose_html_for_source_details_components(bibliographic_record) {
    let html = "";
    if (bibliographic_record.components) {
      html += '<ul class="bibliographic-details-components">';
      bibliographic_record.components.forEach(function (component) {
        html += compose_html_for_source_details_component(component);
      });
      html += "</ul>";
    }
    return html;
  }


  function compose_html_for_source_details_component(bibliographic_record_component) {
    let html = '<li class="bibliographic-details-component">';
    if (bibliographic_record_component.type === "author") {
      html += `<h4>${bibliographic_record_component.header}</h4>`;
    } else {
      html += `<h4><i>${bibliographic_record_component.header}</i></h4>`;
    }
    if (bibliographic_record_component.occupation) {
      html += `<div class=\"bibliographic-details-occupation\">${bibliographic_record_component.occupation}</div>`;
    }
    html += '<ul class="bibliographic-details-links">';
    if (bibliographic_record_component.odnb_id) {
      const url = ODNB_URL_TEMPLATE + bibliographic_record_component.odnb_id;
      html += `<li><a href=\"${url}\" target="new">View article in <i>Oxford Dictionary of National Biography</i></a></li>`;
    }
    if (bibliographic_record_component.anb_id) {
      const url = ANB_URL_TEMPLATE + bibliographic_record_component.anb_id;
      html += `<li><a href=\"${url}\" target="new">View article in <i>American National Biography</i></a></li>`;
    }
    bibliographic_record_component.more_quotations_queries.forEach(function (query_ingredients) {
      html += compose_html_for_more_quotations_query(query_ingredients);
    });
    html += "</ul></li>";
    return html;
  }


  function compose_html_for_more_quotations_query(query_ingredients) {
    const url = QUOTATION_QUERY_TEMPLATE + query_ingredients.query_string;
    let surface_text;
    if (query_ingredients.search_type === "author") {
      surface_text = `by ${query_ingredients.label}`;
    } else {
      surface_text = `from <i>${query_ingredients.label}</i>`;
    }
    return `<li><a href=\"${url}\">Find more quotations ${surface_text}</a></li>`;
  }


  function populate_source_details_modal_with_null_content(modal, record_id) {
    // If for some reason the required bibliographic record has not been retrieved,
    //  we remove any previous content in the modal, and replace it with nothing.
    //  - This is not great, but better than the user seeing the wrong record
    //    left over from last time.
    modal.find("div#quotation-source-details-bibliographic").text("");
    modal.find("div#quotation-source-details-components").html("");
  }




  // ----------------------------------
  // Historical Thesaurus category
  // ----------------------------------

  $("button.historical-thesaurus-button").click(function () {
    // Expand/collapse a thesaurus panel embedded in a dictionary sense
    const button = $(this),
      sense = button.closest("div.item-body"),
      thesaurus_panel = sense.find(".thesaurus-panel");
    if (thesaurus_panel.hasClass("hidden")) {
      thesaurus_panel.removeClass("hidden");
      thesaurus_panel.addClass("shown");
      change_thesaurus_expand_button_state(button, "true");
    } else {
      thesaurus_panel.removeClass("shown");
      thesaurus_panel.addClass("hidden");
      change_thesaurus_expand_button_state(button, "false");
    }
  });


  $("button.thesaurus-category-expand-button").click(function () {
    // Expand/collapse the body of a child thesaurus category in
    //  a nested tree of thesaurus categories
    const button = $(this),
      category_container = button.closest("li"),
      category_content = category_container.children("div.thesaurus-category").first();
    if (category_content.hasClass("hidden")) {
      category_content.removeClass("hidden").addClass("shown");
      change_thesaurus_expand_button_state(button, "true");
    } else {
      category_content.removeClass("shown").addClass("hidden");
      change_thesaurus_expand_button_state(button, "false");
    }
  });


  $("button.thesaurus-category-expand-all-button").click(function () {
    // Expand all the child thesaurus categories in a nested
    //  tree of thesaurus categories
    const button = $(this);
    // expand all the categories
    $("div.thesaurus-category").each(function () {
      $(this).removeClass("hidden").addClass("shown");
    });
    // change the state of all the expand/collapse buttons
    change_thesaurus_expand_button_state(button, "true");
    $("button.thesaurus-category-expand-button").each(function () {
      change_thesaurus_expand_button_state($(this), "true");
    });
  });


  $("button.thesaurus-category-collapse-all-button").click(function () {
    // Collapse all the child thesaurus categories in a nested
    //  tree of thesaurus categories
    const button = $(this);
    // collapse all the categories
    $("div.thesaurus-category").each(function () {
      if (!$(this).hasClass("thesaurus-parent-category")) {
        $(this).removeClass("shown").addClass("hidden");
      }
    });
    // change the state of all the expand/collapse buttons
    change_thesaurus_expand_button_state(button, "false");
    $("button.thesaurus-category-expand-button").each(function () {
      change_thesaurus_expand_button_state($(this), "false");
    });
  });


  function change_thesaurus_expand_button_state(button, state) {
    // Change the state of a thesaurus category expand/collapse button
    button.attr("aria-expanded", state);
    button.addClass("expanded");
    if (state === "false") {
      button.removeClass("expanded");
    }
  }


  $(document).on("change", "div.thesaurus-instance-sorter select", function () {
    // Sort thesaurus instances by date or by alphabetical order

    const get_sort_mode = function (selector_element) {
      let mode = "";
      selector_element.find("option:selected").each(function () {
        mode = $(this).val();
      });
      return mode;
    };

    const sort_alpha = function (x, y) {
      const x_alpha = $(x).data("sort-alpha"),
        y_alpha = $(y).data("sort-alpha");
      if (x_alpha < y_alpha) {
        return -1;
      } else {
        return 1;
      }
    };

    const sort_date = function (x, y) {
      const x_date = parseInt($(x).data("sort-date")),
        y_date = parseInt($(y).data("sort-date"));
      if (x_date < y_date) {
        return -1;
      } else {
        return 1;
      }
    };

    const select = $(this),
      sort_mode = get_sort_mode(select);
    $("ol.thesaurus-instance-list").each(function () {
      const list_items = $(this).children("li");
      if (sort_mode === "sort_alpha") {
        list_items.sort(sort_alpha).appendTo($(this));
      } else if (sort_mode === "sort_date") {
        list_items.sort(sort_date).appendTo($(this));
      }
    });
  });




  // ----------------------------------
  // Historical Thesaurus taxonomy
  // ----------------------------------

  $("a.thesaurus-taxonomy-link").click(function () {
    event.preventDefault();
    const clicked_link = $(this);
    // remove any existing "current" class from <a> links in the taxonomy...
    $("li.thesaurus-taxonomy-item.current").removeClass("current");
    // ... then add "current" class to <li> items directly
    //  above the link that has just been clicked.
    clicked_link.parents("li.thesaurus-taxonomy-item").addClass("current");
  });




  // ----------------------------------
  // Quotations
  // ----------------------------------

  // Hide or show all quotation blocks on page load
  hide_or_show_all_quotation_blocks();


  function hide_or_show_all_quotation_blocks() {
    // Hide or show all quotation blocks in the entry, depending on the value
    //  of the 'hide-all-quotations' parameter in the page URL (true or false/omitted) 
    const url_params = new URLSearchParams(window.location.search);
    if (url_params.has("hide-all-quotations") && url_params.get("hide-all-quotations") === "true") {
      $("button.show-quotations").show();
      $("button.hide-quotations").hide();
      $("div.quotation-block").removeClass("hidden").addClass("show");
    } else {
      $("button.show-quotations").show();
      $("button.hide-quotations").hide();
      $("div.quotation-block").removeClass("shown").addClass("hidden");
    } 
  };


  $("button.show-quotations").on("click", function () {
    // Show all quotation blocks in this sense
    $(this).parent().find("div.quotation-block").removeClass("hidden").addClass("shown");
    $(this).hide();
    $(this).parent().find("button.hide-quotations").show();
  });


  $("button.hide-quotations").on("click", function () {
    // Hide all quotation blocks in this sense
    $(this).parent().find("div.quotation-block").removeClass("shown").addClass("hidden");
    $(this).hide();
    $(this).parent().find("button.show-quotations").show();
  });


  $("input.hide-all-quotations").click(function () {
    // Update the 'hide-all-quotations' parameter in the page URL, in response
    //  to the user clicking the 'hide-all-quotations' checkbox in the sidebar.
    //  This causes the page to reload with the new 'hide-all-quotations'
    //  parameter value, which in turn trigger the hide_or_show_all_quotation_blocks()
    //  function above.
    const url_params = new URLSearchParams(window.location.search);
    if ($(this).is(":checked")) {
      url_params.set("hide-all-quotations", "true");
    } else {
      url_params.delete("hide-all-quotations");
    }
    window.location.search = url_params.toString();
  });




  // ----------------------------------
  // Etymology
  //  - Show more/show less of the etymology when in non-tabbed mode
  // ----------------------------------

  $("button#main_etymology_show_more").on("click", function () {
    $("section#etymology div#main_etymology_complete").removeClass("hidden");
    $("section#etymology div#main_etymology_truncated").addClass("hidden");
  });


  $("button#main_etymology_show_less").on("click", function () {
    $("section#etymology div#main_etymology_complete").addClass("hidden");
    $("section#etymology div#main_etymology_truncated").removeClass("hidden");
  });




  // ----------------------------------
  // Frequency
  //  - Show more/show less of the frequency when in non-tabbed mode
  // ----------------------------------

  $("div#frequency_controls > button").on("click", function () {
    const button_id = $(this).attr("id");
    if (button_id === "frequency_series_show_more") {
      $("section#frequency div#frequency_series").removeClass("hidden");
    } else {
      $("section#frequency div#frequency_series").addClass("hidden");
    }
    // Toggle buttons: Hide this button and show the other one
    $(this).parent().find("button").removeClass("hidden");
    $(this).addClass("hidden");
  });




  // ----------------------------------
  // Variant forms
  // ----------------------------------

  $("select.variant-forms-filter").change(function () {
    // Filter out variant-form table rows whose dates are outside
    //  the from--to date-range selected by the user
    const lower_boundary = parseInt($("select#variant-forms-filter-from").val()),
      upper_boundary = parseInt($("select#variant-forms-filter-to").val());

    $("li.variant-forms-set").each(function () {
      const start_date = parseInt($(this).data("start-date")),
        end_date = parseInt($(this).data("end-date"));
      // Display the row by default...
      $(this).removeClass("hidden");
      // ... but hide the row if its date-range is outside the range selected
      if (start_date > upper_boundary || end_date < lower_boundary) {
        $(this).addClass("hidden");
      }
    });
  });




  // ----------------------------------
  // Table of contents
  // ----------------------------------

  $(".toc-expand-button").on("click", function () {
    const parent_elt = $(this).parent();
    let child_elt = parent_elt.children("ol").first();
    if (!child_elt.length) {
      child_elt = parent_elt.children("div.nested-sidebar").first();
    }

    if (child_elt.length && child_elt.hasClass("hidden")) {
      child_elt.removeClass("hidden");
      child_elt.addClass("shown");
      $(this).text("Collapse");
      $(this).addClass("expanded");
    } else if (child_elt.length) {
      child_elt.removeClass("shown");
      child_elt.addClass("hidden");
      $(this).text("Expand");
      $(this).removeClass("expanded");
    }
  });


  // Set table-of-contents highlighting at initial page-load...
  update_table_of_contents_highlight()
  // ...and then re-set every time the page is scrolled
  $(window).scroll(function (event) { update_table_of_contents_highlight(); });


  function update_table_of_contents_highlight() {
    // Find the sense that is currently in the viewport...
    const sense = find_sense_in_viewport();

    // ...check if this has changed since the last time we checked...
    let sense_id;
    if (sense) {
      sense_id = String(sense.attr("id"));
    } else {
      sense_id = null;
    }
    // ... skip if it *hasn't* changed...
    if (sense_id === id_of_sense_in_viewport) { return; }

    // ...or highlight the corresponding item in the table-of-contents
    //  if it *has* changed.
    highlight_links_in_table_of_contents(sense, sense_id);
    id_of_sense_in_viewport = sense_id;
  }


  function find_sense_in_viewport() {
    // Determine which sense (if any) is currently in the viewport
    const viewport_height = $(window).height(),
      top_of_viewport = $(document).scrollTop(),
      bottom_of_viewport = top_of_viewport + viewport_height,
      top_quarter_of_viewport = top_of_viewport + (viewport_height / 4);
    // Loop through the list of senses, looking for the first one that is
    //  in the viewport.
    let matching_sense = null;
    sense_elements.each(function () {
      const sense = $(this),
        sense_top_offset = sense.offset().top,
        sense_bottom_offset = sense_top_offset + sense.outerHeight(); 
      if (
          // a sense that is covering all of the viewport...
          (sense_top_offset < top_of_viewport && sense_bottom_offset > bottom_of_viewport)
          // ...or a sense whose top is near the top of the viewport
          || (sense_top_offset > top_of_viewport && sense_top_offset < top_quarter_of_viewport)
      ) {
        matching_sense = sense;
        return false;  // break out of the loop
      }
    });
    if (matching_sense) { return matching_sense; }

    // If we've failed to find a sense whose top or middle is in the viewport,
    //  we fall back to looking for a sense whose *bottom* is in the viewport.
    sense_elements.each(function () {
      const sense = $(this),
        sense_offset = sense.offset(),
        sense_offset_bottom = sense_offset.top + sense.outerHeight(); 
      if  (sense_offset_bottom > top_of_viewport && sense_offset_bottom < bottom_of_viewport) {
        matching_sense = sense;
        return false;  // break out of the loop
      }
    });

    return matching_sense;
  }


  function highlight_links_in_table_of_contents(sense, sense_id) {
    // Given a sense element (<li class="sense">) from the main content panel,
    //  add a highlight class to the <a> element in the sidebar table-of-contents
    //  that links to this sense (and to any <a> element that is
    //  a parent/ancestor of this).
    const highlight_class = "table-of-contents-highlight";

    // Remove all highlighting from all <a> elements in the table-of-contents...
    for (var key in table_of_contents_mappings) {
      table_of_contents_mappings[key].removeClass(highlight_class);
    }
    if (! sense) { return false; }

    // ...then highlight the table-of-contents <a> element that corresponds
    //  to the input sense (and to any <a> element that is
    //  a parent/ancestor of this).
    const toc_link_element = table_of_contents_mappings[sense_id];
    if (! toc_link_element) { return false; }
    toc_link_element.parents("li.toc-item").each(function () {
      $(this).children("a").addClass(highlight_class);
    });
    return true;
  }




  // ----------------------------------
  // Pronunciation key
  // ----------------------------------

  $("button#pronunciation_key_wrapper_button").click(function () {
    const section = $(this).closest("section"),
      toggleable_div = section.find("#pronunciation_keys_wrapper");
    toggle_pronunciation_key($(this), toggleable_div);
  });


  $("button.pronunciation-key-button").click(function () {
    const group_div = $(this).closest("div.pronunciation-key-group"),
      toggleable_div = group_div.find(".pronunciation-key");
    toggle_pronunciation_key($(this), toggleable_div);
  });


  function toggle_pronunciation_key(button, toggleable_div) {
    // Hide or display the pronunciation key for a particular region
    //  (U.S. pronunciation key, Australian pronunciation key, etc.).
    if (toggleable_div.hasClass("hidden")) {
      // show
      toggleable_div.removeClass("hidden");
      toggleable_div.addClass("shown");
      button.attr("aria-expanded", "true");
    } else {
      // hide
      toggleable_div.removeClass("shown");
      toggleable_div.addClass("hidden");
      button.attr("aria-expanded", "false");
    }
  }




  // ----------------------------------
  // Compounds & derivatives view switch
  // ----------------------------------

  $("fieldset.compounds-view-controls").find("input").click(function () {
    // Switch between list view and curated view
    const selected_view = $(this).attr("value");
    const identifier = get_compounds_section_identifier(selected_view);

    // Synchronize state of both sets of compounds-view-controls radio-buttons
    $("fieldset.compounds-view-controls").find("input").each(function () {
      if ($(this).attr("value") === selected_view) {
        $(this).prop("checked", "checked");
      }
    });

    // Hide one view and unhide the other
    $("div.compounds-view-sidebar").addClass("hidden");
    $(`div#${identifier}_sidebar`).removeClass("hidden");
    $("div.compounds-view-body").addClass("hidden");
    $(`div#${identifier}_body`).removeClass("hidden");
  });


  function get_compounds_section_identifier(selected_view) {
    if (selected_view === "all") {
      return "compounds_list_view";
    } else {
      return "compounds_curated_view";
    }
  }




  // ----------------------------------
  // Factsheet
  // ----------------------------------

  $(document).on("click", "a.factsheet-onward-link", function (event) {
    // Go the right target tab or section when one of the Factsheet's
    //  "See ..." links is clicked.
    //
    // N.b. we have to do
    //   $(document).on("click", "a.factsheet-onward-link", function () {...})
    // because links may get added dynamically after page-load (as part of
    // the population of the Factsheet modal when in non-tabbed mode); these
    // dynamically added links would be missed by the more usual
    //   $("a.factsheet-onward-link").click(function () {...})
    event.preventDefault();

    const tab_identifier = $(this).data("target-tab"),
      target_url = this.href;
    if (get_current_tabbed_mode() === "tabbed") {
      // When in tabbed mode, switch from the factsheet tab to the target tab.
      window.location.href = target_url;
    } else {
      // When in non-tabbed mode, the factsheet is in a modal; so we close
      //  the factsheet modal, then scroll to the right point on the page.
      close_modal($(this));
      $(`section#${tab_identifier}`).get(0).scrollIntoView();
    }
  });


  $(document).on("click", "a.factsheet-subject-tag", function (event) {
    // Go the right target tab or section when one of the Factsheet's
    //  topic links is clicked.
    //
    // N.b. we have to do
    //   $(document).on("click", "a.factsheet-subject-tag", function () {...})
    // for the same reason as for a.factsheet-onward-link above.
    event.preventDefault();

    const target_url = this.href,
      target_element = $(this).data("target-element");
    if (get_current_tabbed_mode() === "tabbed") {
      // When in tabbed mode, switch from the Factsheet tab to the target tab.
      window.location.href = target_url;
    } else {
      // When in non-tabbed mode, the Factsheet is in a modal; so we close
      //  the Factsheet modal, then scroll to the right point on the page.
      close_modal($(this));
      $(`#${target_element}`).get(0).scrollIntoView();
    }
  });




  // ----------------------------------
  // Frequency tables
  // ----------------------------------

  $(".frequency-table-expand-button").on("click", function () {
    const table_section = $(this).parent();
    const table = table_section.find(".frequency-table");
    if (table.hasClass("hidden")) {
      table.removeClass("hidden");
      table.addClass("shown");
      $(this).attr("aria-expanded", "true");
    } else {
      table.removeClass("shown");
      table.addClass("hidden");
      $(this).attr("aria-expanded", "false");
    }
  });




  // ----------------------------------
  // Frequency charts
  // ----------------------------------

  // Colour and font settings
  const frequency_chart_config = {
    line_background_color: "rgba(230, 230, 230, 0.5)", // grey
    line_border_color: {
      google_ngrams: "#9B1827", // deep red
google_ngrams01: "#9B1827", // deep red
google_ngrams02: "#9B1827", // deep red
google_ngrams03: "#9B1827", // deep red
google_ngrams04: "#9B1827", // deep red
google_ngrams05: "#9B1827", // deep red
google_ngrams06: "#9B1827", // deep red
google_ngrams07: "#9B1827", // deep red
google_ngrams08: "#9B1827", // deep red
google_ngrams09: "#9B1827", // deep red
google_ngrams10: "#9B1827", // deep red
google_ngrams11: "#9B1827", // deep red
google_ngrams12: "#9B1827", // deep red
google_ngrams13: "#9B1827", // deep red
google_ngrams14: "#9B1827", // deep red
google_ngrams15: "#9B1827", // deep red
google_ngrams16: "#9B1827", // deep red
google_ngrams17: "#9B1827", // deep red
google_ngrams18: "#9B1827", // deep red
google_ngrams19: "#9B1827", // deep red
google_ngrams20: "#9B1827", // deep red
google_ngrams21: "#9B1827", // deep red
google_ngrams22: "#9B1827", // deep red
google_ngrams23: "#9B1827", // deep red
      komodo: "#189b8c", // turquoise
      komodo01: "#189b8c", // turquoise
      komodo02: "#189b8c", // turquoise
      komodo03: "#189b8c", // turquoise
      komodo04: "#189b8c", // turquoise
      komodo05: "#189b8c", // turquoise
      komodo06: "#189b8c", // turquoise
      komodo07: "#189b8c", // turquoise
      komodo08: "#189b8c", // turquoise
      komodo09: "#189b8c", // turquoise
      komodo10: "#189b8c", // turquoise
      komodo11: "#189b8c", // turquoise
      komodo12: "#189b8c", // turquoise
      komodo13: "#189b8c", // turquoise
      komodo14: "#189b8c", // turquoise
      komodo15: "#189b8c", // turquoise
      komodo16: "#189b8c", // turquoise
      komodo17: "#189b8c", // turquoise
      komodo18: "#189b8c", // turquoise
      komodo19: "#189b8c", // turquoise
      komodo20: "#189b8c", // turquoise
      komodo21: "#189b8c", // turquoise
      komodo22: "#189b8c", // turquoise
      komodo23: "#189b8c", // turquoise
    },
    scale_text_color: "#000", // black
    sparkline_axis_color: "#797A7D", // dark grey
    label_font_size: 15,
    tick_font_size: 13,
    sparkline_canvas_id: "factsheet-sparkline-chart",
    font_family: "Open Sans",
  };


  create_frequency_charts();
  create_sparkline(frequency_chart_config.sparkline_canvas_id);


  function create_frequency_charts() {
    // Create frequency charts for Google Ngrams and Komodo
    const sources = ["google_ngrams", "komodo", "google_ngrams01", "google_ngrams02", "google_ngrams03", "google_ngrams04", "google_ngrams05", "google_ngrams06", "google_ngrams07", "google_ngrams08", "google_ngrams09", "google_ngrams10", "google_ngrams11", "google_ngrams12", "google_ngrams13", "google_ngrams14", "google_ngrams15", "google_ngrams16", "google_ngrams17", "google_ngrams18", "google_ngrams19", "google_ngrams20", "google_ngrams21", "google_ngrams22", "google_ngrams23", "komodo01", "komodo02", "komodo03", "komodo04", "komodo05", "komodo06", "komodo07", "komodo08", "komodo09", "komodo10", "komodo11", "komodo12", "komodo13", "komodo14", "komodo15", "komodo16", "komodo17", "komodo18", "komodo19", "komodo20", "komodo21", "komodo22", "komodo23"];
       for (var i = 0; i < sources.length; i += 1) {
      const source = sources[i],
        frequency_table_id = `frequency-table-${source}`,
        frequency_table = $(`table#${frequency_table_id}`),
        frequency_data = get_frequency_data(frequency_table);
      if (frequency_data) {
        create_frequency_chart(source, frequency_data);
      }
    }
  }


  function create_sparkline(sparkline_canvas_id) {
    // Create frequency sparkline to go in the factsheet
    const frequency_table = $("table#factsheet-sparkline-data-table").first(),
      frequency_data = get_frequency_data(frequency_table);
    if (frequency_data) {
      create_sparkline_chart(frequency_data, sparkline_canvas_id);
    }
  }


  function get_frequency_data(frequency_table) {
    // Collect years and corresponding frequency values from the table of frequency values
    if (!frequency_table) {
      return;
    } else {
      const frequency_data = {
        years: [],
        frequencies: [],
      };
      frequency_table
        .find("tbody")
        .children("tr")
        .each(function () {
          const year = $(this).find("td").first().text();
          const frequency = parseFloat($(this).find("td").last().text());
          frequency_data.years.push(year);
          frequency_data.frequencies.push(frequency);
        });
      return frequency_data;
    }
  }


  function create_frequency_chart(source, frequency_data) {
    const frequency_chart_id = `frequency-chart-${source}`;
    const canvas = document.getElementById(frequency_chart_id);
    if (!canvas) {
      return;
    } else {
      render_frequency_chart(canvas.getContext("2d"), frequency_data, source);
    }
  }


  function render_frequency_chart(canvas, frequency_data, source) {
    const local_config = {
      title_font: {
        weight: "600",
        size: frequency_chart_config.label_font_size,
      },
      ticks: {
        font: {
          size: frequency_chart_config.tick_font_size,
        },
      },
      point: {
        backgroundColor: "#fff",
        borderColor: frequency_chart_config.line_border_color[source],
        borderWidth: 2,
        hoverRadius: 4,
        hoverBorderWidth: 2,
        radius: 4,
      },
      line: {
        borderColor: frequency_chart_config.line_border_color[source],
        borderWidth: 2,
        fill: true,
        backgroundColor: frequency_chart_config.line_background_color,
      },
      tooltip: {
        displayColors: false,
        padding: 8,
        titleFont: {
          size: frequency_chart_config.label_font_size,
        },
        bodyFont: {
          size: frequency_chart_config.label_font_size,
        },
      },
      maximum_value: Math.max(...frequency_data.frequencies),
    };

    Chart.defaults.font.size = 14;
    Chart.defaults.font.family = frequency_chart_config.font_family;

    const frequency_chart = new Chart(canvas, {
      type: "line",
      data: {
        labels: frequency_data.years,
        datasets: [
          {
            data: frequency_data.frequencies,
            label: "Frequency per million words",
            cubicInterpolationMode: "default",
            tension: 0.25,
          },
        ],
      },
      options: {
        animation: {
          duration: 0,
        },
        scales: {
          y: {
            beginAtZero: true,
            title: {
              display: true,
              text: "Frequency*",
              color: frequency_chart_config.scale_text_color,
              padding: 15,
              font: local_config.title_font,
            },
            ticks: local_config.ticks,
            // Make sure the top of the chart is always a bit higher than the maximum
            //  value in the frequency series
            suggestedMax: local_config.maximum_value * 1.1,
          },
          x: {
            title: {
              display: true,
              text: source === "google_ngrams" ? "Decade" : "Period",
              color: frequency_chart_config.scale_text_color,
              padding: {
                top: 15,
              },
              font: local_config.title_font,
            },
            ticks: local_config.ticks,
          },
        },
        elements: {
          point: local_config.point,
          line: local_config.line,
        },
        plugins: {
          legend: {
            display: false,
          },
          tooltip: local_config.tooltip,
        },
      },
    });
  }


  function create_sparkline_chart(frequency_data, sparkline_canvas_id) {
    const canvas = document.getElementById(sparkline_canvas_id);
    if (!canvas) {
      return;
    } else {
      render_sparkline_chart(canvas.getContext("2d"), frequency_data);
    }
  }


  function render_sparkline_chart(canvas, frequency_data) {
    const sparkline_grid_config = {
      display: false,
      color: frequency_chart_config.sparkline_axis_color,
      borderColor: frequency_chart_config.sparkline_axis_color,
    };

    Chart.defaults.font.size = 8;
    Chart.defaults.font.family = frequency_chart_config.font_family;

    const frequency_chart = new Chart(canvas, {
      type: "line",
      data: {
        labels: frequency_data.years,
        datasets: [
          {
            data: frequency_data.frequencies,
            label: "Frequency",
            cubicInterpolationMode: "default",
            tension: 0.25,
          },
        ],
      },
      options: {
        animation: {
          duration: 0,
        },
        scales: {
          y: {
            beginAtZero: true,
            ticks: {
              display: false,
            },
            grid: sparkline_grid_config,
          },
          x: {
            ticks: {
              display: true,
            },
            grid: sparkline_grid_config,
          },
        },
        elements: {
          point: {
            radius: 0,
          },
          line: {
            borderColor: frequency_chart_config.line_border_color.historical,
            borderWidth: 1,
            fill: true,
            backgroundColor: frequency_chart_config.line_background_color,
          },
        },
        plugins: {
          legend: {
            display: false,
          },
          tooltip: {
            enabled: false,
          },
        },
      },
    });
  }




  // ----------------------------------
  // Cross-reference tooltips
  // - see https://atomiks.github.io/tippyjs/
  // ----------------------------------

  tippy.setDefaultProps({
    delay: [625, 100], // = [delay in ms before show, delay in ms before hide]
    animation: "shift-away",
    inlinePositioning: true,
  });
  tippy("a.cross-reference[data-tippy-content]");




  // ----------------------------------
  // Autocomplete scope
  // ----------------------------------

  $("#autocompleteScope").change(function () {
    if ($(this).val() === "dictionaryScope") {
      $("#autocompleteResultsThesaurus").hide();
      $("#autocompleteResultsDictionary").show();
    } else {
      $("#autocompleteResultsDictionary").hide();
      $("#autocompleteResultsThesaurus").show();
    }
  });




  // ----------------------------------
  // Sticky panels
  // ----------------------------------

  $(window).scroll(function (event) {
    const heights = {
      header_height: Math.floor($(".siteHeader").outerHeight()),
      entry_head_height: Math.floor($(".entry-head").outerHeight()),
      thesaurus_head_height: Math.floor($(".thesaurusHead").outerHeight()),
      tab_buttons_height: Math.floor($("#entry-content-tab-bar").outerHeight()),
      footer_height: Math.floor($("footer").outerHeight()),
      y: $(window).scrollTop(),
    };

    fix_entry_content(heights);
    fix_thesaurus_body(heights);
    fix_entry_mobile_nav(heights);
    fix_thesaurus_mobile_nav(heights);
    fix_information_mobile_nav(heights);
  });


  function fix_entry_content(heights) {
    const entry_fix_height = heights.header_height + heights.entry_head_height,
      viewport = window.innerHeight,
      active_tab_content = $(".tab-section:not(.hidden) .tab-content"),
      active_tab_content_body = active_tab_content.find(".tab-content-body");

    // Get the height of active_tab_content_body if it exists; or else fall back
    //  to the height of active_tab_content if not.
    // Add footer height
    // This establishes the total content height available once elements are fixed
    let active_tab_content_height;
    if (active_tab_content_body.length > 0) {
      active_tab_content_height = active_tab_content_body.outerHeight() + heights.footer_height;
    } else {
      active_tab_content_height = active_tab_content.outerHeight() + heights.footer_height;
    }

    // Fix tab bar and sidebar for current tab when in tabbed view
    if (heights.y >= entry_fix_height && active_tab_content_height > viewport) {
      // Check that the content that remains in the main body once elements are fixed is bigger
      //  than the viewport. This avoids nasty juddering scroll jank.
      // Fix tab buttons
      $("#entry-content-tab-bar").addClass("fixed");
      // Fix sidebars in both views
      $(".tab-content-sidebar")
        .addClass("fixed")
        .css("top", heights.tab_buttons_height + 1);
      // Compensate for removal of tab buttons
      $(".tab-content").css("margin-top", heights.tab_buttons_height);
    } else {
      // Unfix tab buttons
      $("#entry-content-tab-bar").removeClass("fixed");
      // Unfix sidebars in both views
      $(".tab-content-sidebar").removeClass("fixed");
      // Compensate for removal of tab buttons from main body
      $(".tab-content").removeAttr("style");
    }

    // Fix sidebar for entry when in non-tabbed view; in this case we don't check
    //  that the content_height > viewport, since we can assume that
    //  non-tabbed content (~ the whole entry) is *always* greater than the viewport.
    if (heights.y >= entry_fix_height) {
      $("#entry_content_sidebar").addClass("fixed").css("top", 0);
    } else {
      $("#entry_content_sidebar").removeClass("fixed");
    }

  }


  function fix_thesaurus_body(heights) {
    const thesaurus_fix_height = heights.header_height + heights.thesaurus_head_height;
    // alert (thesaurus_fix_height);
    if (heights.y >= thesaurus_fix_height) {
      $(".thesaurusBody .sidebar").addClass("fixed");
      $(".thesaurusBody .sidebar").css("top", 0);
    } else {
      $(".thesaurusBody .sidebar").removeClass("fixed");
    }
  }


  function fix_entry_mobile_nav(heights) {
    const mobile_nav_fix_height = heights.header_height + heights.entry_head_height + heights.tab_buttons_height;
    if(!isNaN(mobile_nav_fix_height)) {
	    // alert (mobile_nav_fix_height);
	     if (heights.y >= mobile_nav_fix_height) {
	      $(".entry-mobile-nav").addClass("fixed");
	    } else {
	      $(".entry-mobile-nav").removeClass("fixed");
	      $("#mobileQuickSearch").hide()
	    }
	  }
  }


  function fix_thesaurus_mobile_nav(heights) {
	  const mobile_nav_fix_height = heights.header_height + heights.thesaurus_head_height;
    if(!isNaN(mobile_nav_fix_height)){
	    // alert (mobile_nav_fix_height);
	    if (heights.y >= mobile_nav_fix_height) {
	      $(".thesaurusMobileNav, .mobileQuickSearch").addClass("fixed");
	    } else {
	      $(".thesaurusMobileNav, .mobileQuickSearch").removeClass("fixed");
	      $("#mobileQuickSearch").hide();
	    }
  	}
  }

  function fix_information_mobile_nav(heights) {
    const mobile_nav_fix_height = heights.header_height;
    if(!isNaN(mobile_nav_fix_height)){
      // alert (mobile_nav_fix_height);
      if (heights.y >= mobile_nav_fix_height) {
        $(".informationMobileNav, .mobileQuickSearch").addClass("fixed");
      } else {
        $(".informationMobileNav, .mobileQuickSearch").removeClass("fixed");
        $("#mobileQuickSearch").hide();
      }
  	}
  }


});