text: import $ from "jquery";
  import PlotlyBar from "plotly.js/lib/bar";
  import Plotly from "plotly.js/lib/core";
  import PlotlyPie from "plotly.js/lib/pie";
  import tippy from "tippy.js";
  
  import {$t, $t_html} from "../i18n";
  import {page_params} from "../page_params";
  
  Plotly.register([PlotlyBar, PlotlyPie]);
  
  const font_14pt = {
      family: "Source Sans 3",
      size: 14,
      color: "#000000",
  };
  
  let last_full_update = Number.POSITIVE_INFINITY;
  
  // TODO: should take a dict of arrays and do it for all keys
  function partial_sums(array) {
      let accumulator = 0;
      return array.map((o) => {
          accumulator += o;
          return accumulator;
      });
  }
  
  // Assumes date is a round number of hours
  function floor_to_local_day(date) {
      const date_copy = new Date(date.getTime());
      date_copy.setHours(0);
      return date_copy;
  }
  
  // Assumes date is a round number of hours
  function floor_to_local_week(date) {
      const date_copy = floor_to_local_day(date);
      date_copy.setHours(-24 * date.getDay());
      return date_copy;
  }
  
  function format_date(date, include_hour) {
      const months = [
          $t({defaultMessage: "January"}),
          $t({defaultMessage: "February"}),
          $t({defaultMessage: "March"}),
          $t({defaultMessage: "April"}),
          $t({defaultMessage: "May"}),
          $t({defaultMessage: "June"}),
          $t({defaultMessage: "July"}),
          $t({defaultMessage: "August"}),
          $t({defaultMessage: "September"}),
          $t({defaultMessage: "October"}),
          $t({defaultMessage: "November"}),
          $t({defaultMessage: "December"}),
      ];
      const month_str = months[date.getMonth()];
      const year = date.getFullYear();
      const day = date.getDate();
      if (include_hour) {
          const hour = date.getHours();
  
          const str = hour >= 12 ? "PM" : "AM";
  
          return month_str + " " + day + ", " + (hour % 12) + ":00" + str;
      }
      return month_str + " " + day + ", " + year;
  }
  
  function update_last_full_update(end_times) {
      if (end_times.length === 0) {
          return;
      }
  
      last_full_update = Math.min(last_full_update, end_times.at(-1));
      const update_time = new Date(last_full_update * 1000);
      const locale_date = update_time.toLocaleDateString("en-US", {
          year: "numeric",
          month: "long",
          day: "numeric",
      });
      const locale_time = update_time.toLocaleTimeString().replace(":00 ", " ");
  
      $("#id_last_full_update").text(locale_time + " on " + locale_date);
      $("#id_last_full_update").closest(".last-update").show();
  }
  
  $(() => {
      tippy(".last_update_tooltip", {
          // Same defaults as set in our tippyjs module.
          maxWidth: 300,
          delay: [100, 20],
          animation: false,
          touch: ["hold", 750],
          placement: "top",
      });
      // Add configuration for any additional tooltips here.
  });
  
  function populate_messages_sent_over_time(data) {
      if (data.end_times.length === 0) {
          // TODO: do something nicer here
          return;
      }
  
      // Helper functions
      function make_traces(dates, values, type, date_formatter) {
          const text = dates.map((date) => date_formatter(date));
          const common = {x: dates, type, hoverinfo: "none", text, textposition: "none"};
          return {
              human: {
                  // 5062a0
                  name: $t({defaultMessage: "Humans"}),
                  y: values.human,
                  marker: {color: "#5f6ea0"},
                  ...common,
              },
              bot: {
                  // a09b5f bbb56e
                  name: $t({defaultMessage: "Bots"}),
                  y: values.bot,
                  marker: {color: "#b7b867"},
                  ...common,
              },
              me: {
                  name: $t({defaultMessage: "Me"}),
                  y: values.me,
                  marker: {color: "#be6d68"},
                  ...common,
              },
          };
      }
  
      const layout = {
          barmode: "group",
          width: 750,
          height: 400,
          margin: {l: 40, r: 0, b: 40, t: 0},
          xaxis: {
              fixedrange: true,
              rangeslider: {bordercolor: "#D8D8D8", borderwidth: 1},
              type: "date",
          },
          yaxis: {fixedrange: true, rangemode: "tozero"},
          legend: {
              x: 0.62,
              y: 1.12,
              orientation: "h",
              font: font_14pt,
          },
          font: font_14pt,
      };
  
      function make_rangeselector(x, y, button1, button2) {
          return {
              x,
              y,
              buttons: [
                  {stepmode: "backward", ...button1},
                  {stepmode: "backward", ...button2},
                  {step: "all", label: $t({defaultMessage: "All time"})},
              ],
          };
      }
  
      // This is also the cumulative rangeselector
      const daily_rangeselector = make_rangeselector(
          0.68,
          -0.62,
          {count: 10, label: $t({defaultMessage: "Last 10 days"}), step: "day"},
          {count: 30, label: $t({defaultMessage: "Last 30 days"}), step: "day"},
      );
      const weekly_rangeselector = make_rangeselector(
          0.656,
          -0.62,
          {count: 2, label: $t({defaultMessage: "Last 2 months"}), step: "month"},
          {count: 6, label: $t({defaultMessage: "Last 6 months"}), step: "month"},
      );
  
      function add_hover_handler() {
          document.querySelector("#id_messages_sent_over_time").on("plotly_hover", (data) => {
              $("#hoverinfo").show();
              document.querySelector("#hover_date").textContent =
                  data.points[0].data.text[data.points[0].pointNumber];
              const values = [null, null, null];
              for (const trace of data.points) {
                  values[trace.curveNumber] = trace.y;
              }
              const hover_text_ids = ["#hover_me", "#hover_human", "#hover_bot"];
              const hover_value_ids = ["#hover_me_value", "#hover_human_value", "#hover_bot_value"];
              for (const [i, value] of values.entries()) {
                  if (value !== null) {
                      document.querySelector(hover_text_ids[i]).style.display = "inline";
                      document.querySelector(hover_value_ids[i]).style.display = "inline";
                      document.querySelector(hover_value_ids[i]).textContent = value;
                  } else {
                      document.querySelector(hover_text_ids[i]).style.display = "none";
                      document.querySelector(hover_value_ids[i]).style.display = "none";
                  }
              }
          });
      }
  
      const start_dates = data.end_times.map(
          (timestamp) =>
              // data.end_times are the ends of hour long intervals.
              new Date(timestamp * 1000 - 60 * 60 * 1000),
      );
  
      function aggregate_data(aggregation) {
          let start;
          let is_boundary;
          if (aggregation === "day") {
              start = floor_to_local_day(start_dates[0]);
              is_boundary = function (date) {
                  return date.getHours() === 0;
              };
          } else if (aggregation === "week") {
              start = floor_to_local_week(start_dates[0]);
              is_boundary = function (date) {
                  return date.getHours() === 0 && date.getDay() === 0;
              };
          }
          const dates = [start];
          const values = {human: [], bot: [], me: []};
          let current = {human: 0, bot: 0, me: 0};
          let i_init = 0;
          if (is_boundary(start_dates[0])) {
              current = {
                  human: data.everyone.human[0],
                  bot: data.everyone.bot[0],
                  me: data.user.human[0],
              };
              i_init = 1;
          }
          for (let i = i_init; i < start_dates.length; i += 1) {
              if (is_boundary(start_dates[i])) {
                  dates.push(start_dates[i]);
                  values.human.push(current.human);
                  values.bot.push(current.bot);
                  values.me.push(current.me);
                  current = {human: 0, bot: 0, me: 0};
              }
              current.human += data.everyone.human[i];
              current.bot += data.everyone.bot[i];
              current.me += data.user.human[i];
          }
          values.human.push(current.human);
          values.bot.push(current.bot);
          values.me.push(current.me);
          return {
              dates,
              values,
              last_value_is_partial: !is_boundary(
                  new Date(start_dates.at(-1).getTime() + 60 * 60 * 1000),
              ),
          };
      }
  
      // Generate traces
      let date_formatter = function (date) {
          return format_date(date, true);
      };
      let values = {me: data.user.human, human: data.everyone.human, bot: data.everyone.bot};
  
      let info = aggregate_data("day");
      date_formatter = function (date) {
          return format_date(date, false);
      };
      const last_day_is_partial = info.last_value_is_partial;
      const daily_traces = make_traces(info.dates, info.values, "bar", date_formatter);
  
      info = aggregate_data("week");
      date_formatter = function (date) {
          return $t({defaultMessage: "Week of {date}"}, {date: format_date(date, false)});
      };
      const last_week_is_partial = info.last_value_is_partial;
      const weekly_traces = make_traces(info.dates, info.values, "bar", date_formatter);
  
      const dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
      values = {
          human: partial_sums(data.everyone.human),
          bot: partial_sums(data.everyone.bot),
          me: partial_sums(data.user.human),
      };
      date_formatter = function (date) {
          return format_date(date, true);
      };
      const cumulative_traces = make_traces(dates, values, "scatter", date_formatter);
  
      // Functions to draw and interact with the plot
  
      // We need to redraw plot entirely if switching from (the cumulative) line
      // graph to any bar graph, since otherwise the rangeselector shows both (plotly bug)
      let clicked_cumulative = false;
  
      function draw_or_update_plot(rangeselector, traces, last_value_is_partial, initial_draw) {
          $("#daily_button, #weekly_button, #cumulative_button").removeClass("selected");
          $("#id_messages_sent_over_time > div").removeClass("spinner");
          if (initial_draw) {
              traces.human.visible = true;
              traces.bot.visible = "legendonly";
              traces.me.visible = "legendonly";
          } else {
              const plotDiv = document.querySelector("#id_messages_sent_over_time");
              traces.me.visible = plotDiv.data[0].visible;
              traces.human.visible = plotDiv.data[1].visible;
              traces.bot.visible = plotDiv.data[2].visible;
          }
          layout.xaxis.rangeselector = rangeselector;
          if (clicked_cumulative || initial_draw) {
              Plotly.newPlot(
                  "id_messages_sent_over_time",
                  [traces.me, traces.human, traces.bot],
                  layout,
                  {displayModeBar: false},
              );
              add_hover_handler();
          } else {
              Plotly.deleteTraces("id_messages_sent_over_time", [0, 1, 2]);
              Plotly.addTraces("id_messages_sent_over_time", [traces.me, traces.human, traces.bot]);
              Plotly.relayout("id_messages_sent_over_time", layout);
          }
          $("#id_messages_sent_over_time").attr("last_value_is_partial", last_value_is_partial);
      }
  
      // Click handlers for aggregation buttons
      $("#daily_button").on("click", function () {
          draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, false);
          $(this).addClass("selected");
          clicked_cumulative = false;
      });
  
      $("#weekly_button").on("click", function () {
          draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, false);
          $(this).addClass("selected");
          clicked_cumulative = false;
      });
  
      $("#cumulative_button").on("click", function () {
          clicked_cumulative = false;
          draw_or_update_plot(daily_rangeselector, cumulative_traces, false, false);
          $(this).addClass("selected");
          clicked_cumulative = true;
      });
  
      // Initial drawing of plot
      if (weekly_traces.human.x.length < 12) {
          draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, true);
          $("#daily_button").addClass("selected");
      } else {
          draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, true);
          $("#weekly_button").addClass("selected");
      }
  }
  
  function round_to_percentages(values, total) {
      return values.map((x) => {
          if (x === total) {
              return "100%";
          }
          if (x === 0) {
              return "0%";
          }
          const unrounded = (x / total) * 100;
  
          const precision = Math.min(
              6, // this is the max precision (two #, 4 decimal points; 99.9999%).
              Math.max(
                  2, // the minimum amount of precision (40% or 6.0%).
                  Math.floor(-Math.log10(100 - unrounded)) + 3,
              ),
          );
  
          return unrounded.toPrecision(precision) + "%";
      });
  }
  
  // Last label will turn into "Other" if time_series data has a label not in labels
  function compute_summary_chart_data(time_series_data, num_steps, labels_) {
      const data = new Map();
      for (const [key, array] of Object.entries(time_series_data)) {
          if (array.length < num_steps) {
              num_steps = array.length;
          }
          let sum = 0;
          for (let i = 1; i <= num_steps; i += 1) {
              sum += array.at(-i);
          }
          data.set(key, sum);
      }
      const labels = labels_.slice();
      const values = [];
      for (const label of labels) {
          if (data.has(label)) {
              values.push(data.get(label));
              data.delete(label);
          } else {
              values.push(0);
          }
      }
      if (data.size !== 0) {
          labels[labels.length - 1] = "Other";
          for (const sum of data.values()) {
              values[labels.length - 1] += sum;
          }
      }
      let total = 0;
      for (const value of values) {
          total += value;
      }
      return {
          values,
          labels,
          percentages: round_to_percentages(values, total),
          total,
      };
  }
  
  function populate_messages_sent_by_client(data) {
      const layout = {
          width: 750,
          height: null, // set in draw_plot()
          margin: {l: 3, r: 40, b: 40, t: 0},
          font: font_14pt,
          xaxis: {range: null}, // set in draw_plot()
          yaxis: {showticklabels: false},
          showlegend: false,
      };
  
      // sort labels so that values are descending in the default view
      const everyone_month = compute_summary_chart_data(
          data.everyone,
          30,
          data.display_order.slice(0, 12),
      );
      const label_values = [];
      for (let i = 0; i < everyone_month.values.length; i += 1) {
          label_values.push({
              label: everyone_month.labels[i],
              value: everyone_month.labels[i] === "Other" ? -1 : everyone_month.values[i],
          });
      }
      label_values.sort((a, b) => b.value - a.value);
      const labels = [];
      for (const item of label_values) {
          labels.push(item.label);
      }
  
      function make_plot_data(time_series_data, num_steps) {
          const plot_data = compute_summary_chart_data(time_series_data, num_steps, labels);
          plot_data.values.reverse();
          plot_data.labels.reverse();
          plot_data.percentages.reverse();
          const annotations = {values: [], labels: [], text: []};
          for (let i = 0; i < plot_data.values.length; i += 1) {
              if (plot_data.values[i] > 0) {
                  annotations.values.push(plot_data.values[i]);
                  annotations.labels.push(plot_data.labels[i]);
                  annotations.text.push(
                      "   " + plot_data.labels[i] + " (" + plot_data.percentages[i] + ")",
                  );
              }
          }
          return {
              trace: {
                  x: plot_data.values,
                  y: plot_data.labels,
                  type: "bar",
                  orientation: "h",
                  sort: false,
                  textinfo: "text",
                  hoverinfo: "none",
                  marker: {color: "#537c5e"},
                  font: {family: "Source Sans 3", size: 18, color: "#000000"},
              },
              trace_annotations: {
                  x: annotations.values,
                  y: annotations.labels,
                  mode: "text",
                  type: "scatter",
                  textposition: "middle right",
                  text: annotations.text,
              },
          };
      }
  
      const plot_data = {
          everyone: {
              cumulative: make_plot_data(data.everyone, data.end_times.length),
              year: make_plot_data(data.everyone, 365),
              month: make_plot_data(data.everyone, 30),
              week: make_plot_data(data.everyone, 7),
          },
          user: {
              cumulative: make_plot_data(data.user, data.end_times.length),
              year: make_plot_data(data.user, 365),
              month: make_plot_data(data.user, 30),
              week: make_plot_data(data.user, 7),
          },
      };
  
      let user_button = "everyone";
      let time_button;
      if (data.end_times.length >= 30) {
          time_button = "month";
          $("#messages_by_client_last_month_button").addClass("selected");
      } else {
          time_button = "cumulative";
          $("#messages_by_client_cumulative_button").addClass("selected");
      }
  
      if (data.end_times.length < 365) {
          $("#pie_messages_sent_by_client button[data-time='year']").remove();
          if (data.end_times.length < 30) {
              $("#pie_messages_sent_by_client button[data-time='month']").remove();
              if (data.end_times.length < 7) {
                  $("#pie_messages_sent_by_client button[data-time='week']").remove();
              }
          }
      }
  
      function draw_plot() {
          $("#id_messages_sent_by_client > div").removeClass("spinner");
          const data_ = plot_data[user_button][time_button];
          layout.height = layout.margin.b + data_.trace.x.length * 30;
          layout.xaxis.range = [0, Math.max(...data_.trace.x) * 1.3];
          Plotly.newPlot(
              "id_messages_sent_by_client",
              [data_.trace, data_.trace_annotations],
              layout,
              {displayModeBar: false, staticPlot: true},
          );
      }
  
      draw_plot();
  
      // Click handlers
      function set_user_button(button) {
          $("#pie_messages_sent_by_client button[data-user]").removeClass("selected");
          button.addClass("selected");
      }
  
      function set_time_button(button) {
          $("#pie_messages_sent_by_client button[data-time]").removeClass("selected");
          button.addClass("selected");
      }
  
      $("#pie_messages_sent_by_client button").on("click", function () {
          if ($(this).attr("data-user")) {
              set_user_button($(this));
              user_button = $(this).attr("data-user");
          }
          if ($(this).attr("data-time")) {
              set_time_button($(this));
              time_button = $(this).attr("data-time");
          }
          draw_plot();
      });
  
      // handle links with @href started with '#' only
      $(document).on("click", 'a[href^="#"]', function (e) {
          // target element id
          const id = $(this).attr("href");
          // target element
          const $id = $(id);
          if ($id.length === 0) {
              return;
          }
          // prevent standard hash navigation (avoid blinking in IE)
          e.preventDefault();
          const pos = $id.offset().top + $(".page-content")[0].scrollTop - 50;
          $(".page-content").animate({scrollTop: pos + "px"}, 500);
      });
  }
  
  function populate_messages_sent_by_message_type(data) {
      const layout = {
          margin: {l: 90, r: 0, b: 0, t: 0},
          width: 750,
          height: 300,
          font: font_14pt,
      };
  
      function make_plot_data(time_series_data, num_steps) {
          const plot_data = compute_summary_chart_data(
              time_series_data,
              num_steps,
              data.display_order,
          );
          const labels = [];
          for (let i = 0; i < plot_data.labels.length; i += 1) {
              labels.push(plot_data.labels[i] + " (" + plot_data.percentages[i] + ")");
          }
          const total_string = plot_data.total.toLocaleString();
          return {
              trace: {
                  values: plot_data.values,
                  labels,
                  type: "pie",
                  direction: "clockwise",
                  rotation: -90,
                  sort: false,
                  textinfo: "text",
                  text: plot_data.labels.map(() => ""),
                  hoverinfo: "label+value",
                  pull: 0.05,
                  marker: {
                      colors: ["#68537c", "#be6d68", "#b3b348"],
                  },
              },
              total_html: $t_html(
                  {defaultMessage: "<b>Total messages</b>: {total_messages}"},
                  {total_messages: total_string},
              ),
          };
      }
  
      const plot_data = {
          everyone: {
              cumulative: make_plot_data(data.everyone, data.end_times.length),
              year: make_plot_data(data.everyone, 365),
              month: make_plot_data(data.everyone, 30),
              week: make_plot_data(data.everyone, 7),
          },
          user: {
              cumulative: make_plot_data(data.user, data.end_times.length),
              year: make_plot_data(data.user, 365),
              month: make_plot_data(data.user, 30),
              week: make_plot_data(data.user, 7),
          },
      };
  
      let user_button = "everyone";
      let time_button;
      if (data.end_times.length >= 30) {
          time_button = "month";
          $("#messages_by_type_last_month_button").addClass("selected");
      } else {
          time_button = "cumulative";
          $("#messages_by_type_cumulative_button").addClass("selected");
      }
      const totaldiv = document.querySelector("#pie_messages_sent_by_type_total");
  
      if (data.end_times.length < 365) {
          $("#pie_messages_sent_by_type button[data-time='year']").remove();
          if (data.end_times.length < 30) {
              $("#pie_messages_sent_by_type button[data-time='month']").remove();
              if (data.end_times.length < 7) {
                  $("#pie_messages_sent_by_type button[data-time='week']").remove();
              }
          }
      }
  
      function draw_plot() {
          $("#id_messages_sent_by_message_type > div").removeClass("spinner");
          Plotly.newPlot(
              "id_messages_sent_by_message_type",
              [plot_data[user_button][time_button].trace],
              layout,
              {displayModeBar: false},
          );
          totaldiv.innerHTML = plot_data[user_button][time_button].total_html;
      }
  
      draw_plot();
  
      // Click handlers
      function set_user_button(button) {
          $("#pie_messages_sent_by_type button[data-user]").removeClass("selected");
          button.addClass("selected");
      }
  
      function set_time_button(button) {
          $("#pie_messages_sent_by_type button[data-time]").removeClass("selected");
          button.addClass("selected");
      }
  
      $("#pie_messages_sent_by_type button").on("click", function () {
          if ($(this).attr("data-user")) {
              set_user_button($(this));
              user_button = $(this).attr("data-user");
          }
          if ($(this).attr("data-time")) {
              set_time_button($(this));
              time_button = $(this).attr("data-time");
          }
          draw_plot();
      });
  }
  
  function populate_number_of_users(data) {
      const layout = {
          width: 750,
          height: 370,
          margin: {l: 40, r: 0, b: 65, t: 20},
          xaxis: {
              fixedrange: true,
              rangeslider: {bordercolor: "#D8D8D8", borderwidth: 1},
              rangeselector: {
                  x: 0.64,
                  y: -0.79,
                  buttons: [
                      {
                          count: 2,
                          label: $t({defaultMessage: "Last 2 months"}),
                          step: "month",
                          stepmode: "backward",
                      },
                      {
                          count: 6,
                          label: $t({defaultMessage: "Last 6 months"}),
                          step: "month",
                          stepmode: "backward",
                      },
                      {step: "all", label: $t({defaultMessage: "All time"})},
                  ],
              },
          },
          yaxis: {fixedrange: true, rangemode: "tozero"},
          font: font_14pt,
      };
  
      const end_dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
  
      const text = end_dates.map((date) => format_date(date, false));
  
      function make_traces(values, type) {
          return {
              x: end_dates,
              y: values,
              type,
              name: $t({defaultMessage: "Active users"}),
              hoverinfo: "none",
              text,
              visible: true,
          };
      }
  
      function add_hover_handler() {
          document.querySelector("#id_number_of_users").on("plotly_hover", (data) => {
              $("#users_hover_info").show();
              document.querySelector("#users_hover_date").textContent =
                  data.points[0].data.text[data.points[0].pointNumber];
              const values = [null, null, null];
              for (const trace of data.points) {
                  values[trace.curveNumber] = trace.y;
              }
              const hover_value_ids = [
                  "#users_hover_1day_value",
                  "#users_hover_15day_value",
                  "#users_hover_all_time_value",
              ];
              for (const [i, value] of values.entries()) {
                  if (value !== null) {
                      document.querySelector(hover_value_ids[i]).style.display = "inline";
                      document.querySelector(hover_value_ids[i]).textContent = value;
                  } else {
                      document.querySelector(hover_value_ids[i]).style.display = "none";
                  }
              }
          });
      }
  
      const _1day_trace = make_traces(data.everyone._1day, "bar");
      const _15day_trace = make_traces(data.everyone._15day, "scatter");
      const all_time_trace = make_traces(data.everyone.all_time, "scatter");
  
      $("#id_number_of_users > div").removeClass("spinner");
  
      // Redraw the plot every time for simplicity. If we have perf problems with this in the
      // future, we can copy the update behavior from populate_messages_sent_over_time
      function draw_or_update_plot(trace) {
          $("#1day_actives_button, #15day_actives_button, #all_time_actives_button").removeClass(
              "selected",
          );
          Plotly.newPlot("id_number_of_users", [trace], layout, {displayModeBar: false});
          add_hover_handler();
      }
  
      $("#1day_actives_button").on("click", function () {
          draw_or_update_plot(_1day_trace);
          $(this).addClass("selected");
      });
  
      $("#15day_actives_button").on("click", function () {
          draw_or_update_plot(_15day_trace);
          $(this).addClass("selected");
      });
  
      $("#all_time_actives_button").on("click", function () {
          draw_or_update_plot(all_time_trace);
          $(this).addClass("selected");
      });
  
      // Initial drawing of plot
      draw_or_update_plot(all_time_trace, true);
      $("#all_time_actives_button").addClass("selected");
  }
  
  function populate_messages_read_over_time(data) {
      if (data.end_times.length === 0) {
          // TODO: do something nicer here
          return;
      }
  
      // Helper functions
      function make_traces(dates, values, type, date_formatter) {
          const text = dates.map((date) => date_formatter(date));
          const common = {x: dates, type, hoverinfo: "none", text, textposition: "none"};
          return {
              everyone: {
                  name: $t({defaultMessage: "Everyone"}),
                  y: values.everyone,
                  marker: {color: "#5f6ea0"},
                  ...common,
              },
              me: {
                  name: $t({defaultMessage: "Me"}),
                  y: values.me,
                  marker: {color: "#be6d68"},
                  ...common,
              },
          };
      }
  
      const layout = {
          barmode: "group",
          width: 750,
          height: 400,
          margin: {l: 40, r: 0, b: 40, t: 0},
          xaxis: {
              fixedrange: true,
              rangeslider: {bordercolor: "#D8D8D8", borderwidth: 1},
              type: "date",
          },
          yaxis: {fixedrange: true, rangemode: "tozero"},
          legend: {
              x: 0.62,
              y: 1.12,
              orientation: "h",
              font: font_14pt,
          },
          font: font_14pt,
      };
  
      function make_rangeselector(x, y, button1, button2) {
          return {
              x,
              y,
              buttons: [
                  {stepmode: "backward", ...button1},
                  {stepmode: "backward", ...button2},
                  {step: "all", label: $t({defaultMessage: "All time"})},
              ],
          };
      }
  
      // This is also the cumulative rangeselector
      const daily_rangeselector = make_rangeselector(
          0.68,
          -0.62,
          {count: 10, label: $t({defaultMessage: "Last 10 days"}), step: "day"},
          {count: 30, label: $t({defaultMessage: "Last 30 days"}), step: "day"},
      );
      const weekly_rangeselector = make_rangeselector(
          0.656,
          -0.62,
          {count: 2, label: $t({defaultMessage: "Last 2 months"}), step: "month"},
          {count: 6, label: $t({defaultMessage: "Last 6 months"}), step: "month"},
      );
  
      function add_hover_handler() {
          document.querySelector("#id_messages_read_over_time").on("plotly_hover", (data) => {
              $("#read_hover_info").show();
              document.querySelector("#read_hover_date").textContent =
                  data.points[0].data.text[data.points[0].pointNumber];
              const values = [null, null];
              for (const trace of data.points) {
                  values[trace.curveNumber] = trace.y;
              }
              const read_hover_text_ids = ["#read_hover_me", "#read_hover_everyone"];
              const read_hover_value_ids = ["#read_hover_me_value", "#read_hover_everyone_value"];
              for (const [i, value] of values.entries()) {
                  if (value !== null) {
                      document.querySelector(read_hover_text_ids[i]).style.display = "inline";
                      document.querySelector(read_hover_value_ids[i]).style.display = "inline";
                      document.querySelector(read_hover_value_ids[i]).textContent = value;
                  } else {
                      document.querySelector(read_hover_text_ids[i]).style.display = "none";
                      document.querySelector(read_hover_value_ids[i]).style.display = "none";
                  }
              }
          });
      }
  
      const start_dates = data.end_times.map(
          (timestamp) =>
              // data.end_times are the ends of hour long intervals.
              new Date(timestamp * 1000 - 60 * 60 * 1000),
      );
  
      function aggregate_data(aggregation) {
          let start;
          let is_boundary;
          if (aggregation === "day") {
              start = floor_to_local_day(start_dates[0]);
              is_boundary = function (date) {
                  return date.getHours() === 0;
              };
          } else if (aggregation === "week") {
              start = floor_to_local_week(start_dates[0]);
              is_boundary = function (date) {
                  return date.getHours() === 0 && date.getDay() === 0;
              };
          }
          const dates = [start];
          const values = {everyone: [], me: []};
          let current = {everyone: 0, me: 0};
          let i_init = 0;
          if (is_boundary(start_dates[0])) {
              current = {everyone: data.everyone.read[0], me: data.user.read[0]};
              i_init = 1;
          }
          for (let i = i_init; i < start_dates.length; i += 1) {
              if (is_boundary(start_dates[i])) {
                  dates.push(start_dates[i]);
                  values.everyone.push(current.everyone);
                  values.me.push(current.me);
                  current = {everyone: 0, me: 0};
              }
              current.everyone += data.everyone.read[i];
              current.me += data.user.read[i];
          }
          values.everyone.push(current.everyone);
          values.me.push(current.me);
          return {
              dates,
              values,
              last_value_is_partial: !is_boundary(
                  new Date(start_dates.at(-1).getTime() + 60 * 60 * 1000),
              ),
          };
      }
  
      // Generate traces
      let date_formatter = function (date) {
          return format_date(date, true);
      };
      let values = {me: data.user.read, everyone: data.everyone.read};
  
      let info = aggregate_data("day");
      date_formatter = function (date) {
          return format_date(date, false);
      };
      const last_day_is_partial = info.last_value_is_partial;
      const daily_traces = make_traces(info.dates, info.values, "bar", date_formatter);
  
      info = aggregate_data("week");
      date_formatter = function (date) {
          return $t({defaultMessage: "Week of {date}"}, {date: format_date(date, false)});
      };
      const last_week_is_partial = info.last_value_is_partial;
      const weekly_traces = make_traces(info.dates, info.values, "bar", date_formatter);
  
      const dates = data.end_times.map((timestamp) => new Date(timestamp * 1000));
      values = {everyone: partial_sums(data.everyone.read), me: partial_sums(data.user.read)};
      date_formatter = function (date) {
          return format_date(date, true);
      };
      const cumulative_traces = make_traces(dates, values, "scatter", date_formatter);
  
      // Functions to draw and interact with the plot
  
      // We need to redraw plot entirely if switching from (the cumulative) line
      // graph to any bar graph, since otherwise the rangeselector shows both (plotly bug)
      let clicked_cumulative = false;
  
      function draw_or_update_plot(rangeselector, traces, last_value_is_partial, initial_draw) {
          $("#read_daily_button, #read_weekly_button, #read_cumulative_button").removeClass(
              "selected",
          );
          $("#id_messages_read_over_time > div").removeClass("spinner");
          if (initial_draw) {
              traces.everyone.visible = true;
              traces.me.visible = "legendonly";
          } else {
              const plotDiv = document.querySelector("#id_messages_read_over_time");
              traces.me.visible = plotDiv.data[0].visible;
              traces.everyone.visible = plotDiv.data[1].visible;
          }
          layout.xaxis.rangeselector = rangeselector;
          if (clicked_cumulative || initial_draw) {
              Plotly.newPlot("id_messages_read_over_time", [traces.me, traces.everyone], layout, {
                  displayModeBar: false,
              });
              add_hover_handler();
          } else {
              Plotly.deleteTraces("id_messages_read_over_time", [0, 1]);
              Plotly.addTraces("id_messages_read_over_time", [traces.me, traces.everyone]);
              Plotly.relayout("id_messages_read_over_time", layout);
          }
          $("#id_messages_read_over_time").attr("last_value_is_partial", last_value_is_partial);
      }
  
      // Click handlers for aggregation buttons
      $("#read_daily_button").on("click", function () {
          draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, false);
          $(this).addClass("selected");
          clicked_cumulative = false;
      });
  
      $("#read_weekly_button").on("click", function () {
          draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, false);
          $(this).addClass("selected");
          clicked_cumulative = false;
      });
  
      $("#read_cumulative_button").on("click", function () {
          clicked_cumulative = false;
          draw_or_update_plot(daily_rangeselector, cumulative_traces, false, false);
          $(this).addClass("selected");
          clicked_cumulative = true;
      });
  
      // Initial drawing of plot
      if (weekly_traces.everyone.x.length < 12) {
          draw_or_update_plot(daily_rangeselector, daily_traces, last_day_is_partial, true);
          $("#read_daily_button").addClass("selected");
      } else {
          draw_or_update_plot(weekly_rangeselector, weekly_traces, last_week_is_partial, true);
          $("#read_weekly_button").addClass("selected");
      }
  }
  
  function get_chart_data(data, callback) {
      $.get({
          url: "/json/analytics/chart_data" + page_params.data_url_suffix,
          data,
          idempotent: true,
          success(data) {
              callback(data);
              update_last_full_update(data.end_times);
          },
          error(xhr) {
              $("#id_stats_errors").show().text(JSON.parse(xhr.responseText).msg);
          },
      });
  }
  
  get_chart_data(
      {chart_name: "messages_sent_over_time", min_length: "10"},
      populate_messages_sent_over_time,
  );
  
  get_chart_data(
      {chart_name: "messages_sent_by_client", min_length: "10"},
      populate_messages_sent_by_client,
  );
  
  get_chart_data(
      {chart_name: "messages_sent_by_message_type", min_length: "10"},
      populate_messages_sent_by_message_type,
  );
  
  get_chart_data({chart_name: "number_of_humans", min_length: "10"}, populate_number_of_users);
  
  get_chart_data(
      {chart_name: "messages_read_over_time", min_length: "10"},
      populate_messages_read_over_time,
  );
  