internal/dashboard/frontend/src/composables/useChartData.ts

import {
  buildQueryStringFromState,
  useChartStore,
  type FilterState,
} from "src/store";
import { type HoneypotEvent } from "src/types";
import {
  filterEvents,
  transformToChartPoint,
  type ChartDataPoint,
} from "utils/filters";
import { URL } from "utils/utils";
import {
  computed,
  onBeforeUnmount,
  onMounted,
  ref,
  toValue,
  watch,
  type MaybeRefOrGetter,
} from "vue";
import { useWebSocket } from "./useWebSocket";

export function useChartData(config: {
  onLoaded?: () => void;
  filters?: MaybeRefOrGetter<FilterState>;
  enabled?: MaybeRefOrGetter<boolean>;
}) {
  const chartStore = useChartStore();
  const filters = computed(() => toValue(config.filters) || chartStore.state);
  const enabled = computed(() => toValue(config.enabled) ?? true);

  const queryString = computed(() => {
    return buildQueryStringFromState(filters.value);
  });

  const loading = ref(true);
  const chartData = ref<ChartDataPoint[]>([]);
  const error = ref<Error | null>(null);

  async function fetchData() {
    if (!enabled.value) return;
    loading.value = true;
    error.value = null;
    try {
      const response = await fetch(`${URL()}/api/events?${queryString.value}`);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      const events: HoneypotEvent[] = data.events ?? [];

      // Transform events to chart data points
      chartData.value = events
        .filter((evt) => evt.remote_addr)
        .map(transformToChartPoint)
        .sort((a, b) => a.time.getTime() - b.time.getTime());
      config.onLoaded?.();
    } catch (err) {
      error.value = err instanceof Error ? err : new Error(String(err));
      chartData.value = [];
    } finally {
      loading.value = false;
    }
  }

  const { connect, disconnect, connectionStatus } = useWebSocket(
    (batch: HoneypotEvent[]) => {
      if (!enabled.value) return;
      const filteredEvents = filterEvents(batch, filters.value);

      if (filteredEvents.length === 0) {
        return;
      }

      // Transform all events to chart data points
      const newDataPoints: ChartDataPoint[] = filteredEvents
        .filter((data) => data.remote_addr)
        .map(transformToChartPoint)
        .sort((a, b) => a.time.getTime() - b.time.getTime());

      chartData.value.push(...newDataPoints);
      config.onLoaded?.();
    },
  );

  onMounted(() => {
    if (enabled.value) {
      fetchData();
    }
  });

  onBeforeUnmount(() => {
    chartData.value = [];
  });

  watch(
    () => [queryString.value, enabled.value],
    ([newQs, newEnabled], [oldQs, oldEnabled]) => {
      if (newEnabled && (newQs !== oldQs || !oldEnabled)) {
        fetchData();
      }
    },
  );

  return {
    loading,
    chartData,
    error,
    fetchData,
    queryString,
    connectionStatus,
    connect,
    disconnect,
  };
}