<template>
  <div>
    <div v-if="analytics.error.value" class="universal-card">
      <h2>
        <span class="label__title">Error</span>
      </h2>
      <div>
        {{ analytics.error.value }}
      </div>
    </div>
    <div v-else class="graphs">
      <div class="graphs__vertical-bar">
        <client-only>
          <CompactChart
            v-if="analytics.formattedData.value.downloads"
            ref="tinyDownloadChart"
            :title="`Downloads since ${dayjs(startDate).format('MMM D, YYYY')}`"
            color="var(--color-brand)"
            :value="formatNumber(analytics.formattedData.value.downloads.sum, false)"
            :data="analytics.formattedData.value.downloads.chart.sumData"
            :labels="analytics.formattedData.value.downloads.chart.labels"
            suffix="<svg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' /></svg>"
            :class="`clickable chart-button-base button-base ${
              selectedChart === 'downloads'
                ? 'chart-button-base__selected button-base__selected'
                : ''
            }`"
            :onclick="() => (selectedChart = 'downloads')"
            role="button"
          />
        </client-only>
        <client-only>
          <CompactChart
            v-if="analytics.formattedData.value.views"
            ref="tinyViewChart"
            :title="`Page views since ${dayjs(startDate).format('MMM D, YYYY')}`"
            color="var(--color-blue)"
            :value="formatNumber(analytics.formattedData.value.views.sum, false)"
            :data="analytics.formattedData.value.views.chart.sumData"
            :labels="analytics.formattedData.value.views.chart.labels"
            suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/><circle cx='12' cy='12' r='3'/></svg>"
            :class="`clickable chart-button-base button-base ${
              selectedChart === 'views' ? 'chart-button-base__selected button-base__selected' : ''
            }`"
            :onclick="() => (selectedChart = 'views')"
            role="button"
          />
        </client-only>
        <client-only>
          <CompactChart
            v-if="analytics.formattedData.value.revenue"
            ref="tinyRevenueChart"
            :title="`Revenue since ${dayjs(startDate).format('MMM D, YYYY')}`"
            color="var(--color-purple)"
            :value="formatMoney(analytics.formattedData.value.revenue.sum, false)"
            :data="analytics.formattedData.value.revenue.chart.sumData"
            :labels="analytics.formattedData.value.revenue.chart.labels"
            is-money
            :class="`clickable chart-button-base button-base ${
              selectedChart === 'revenue' ? 'chart-button-base__selected button-base__selected' : ''
            }`"
            :onclick="() => (selectedChart = 'revenue')"
            role="button"
          />
        </client-only>
      </div>
      <div class="graphs__main-graph">
        <div class="universal-card">
          <div class="chart-controls">
            <h2>
              <span class="label__title">
                {{ formatCategoryHeader(selectedChart) }}
              </span>
            </h2>
            <div class="chart-controls__buttons">
              <Button v-tooltip="'Toggle project colors'" icon-only @click="onToggleColors">
                <PaletteIcon />
              </Button>
              <Button v-tooltip="'Download this data as CSV'" icon-only @click="onDownloadSetAsCSV">
                <DownloadIcon />
              </Button>
              <Button v-tooltip="'Refresh the chart'" icon-only @click="resetCharts">
                <UpdatedIcon />
              </Button>
              <DropdownSelect
                v-model="selectedRange"
                :options="selectableRanges"
                name="Time range"
                :display-name="
                  (o: (typeof selectableRanges)[number] | undefined) => o?.label || 'Custom'
                "
              />
            </div>
          </div>
          <div class="chart-area">
            <div class="chart">
              <client-only>
                <Chart
                  v-if="analytics.formattedData.value.downloads && selectedChart === 'downloads'"
                  ref="downloadsChart"
                  type="line"
                  name="Download data"
                  :hide-legend="true"
                  :data="analytics.formattedData.value.downloads.chart.data"
                  :labels="analytics.formattedData.value.downloads.chart.labels"
                  suffix="<svg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' /></svg>"
                  :colors="
                    isUsingProjectColors
                      ? analytics.formattedData.value.downloads.chart.colors
                      : analytics.formattedData.value.downloads.chart.defaultColors
                  "
                />
                <Chart
                  v-if="analytics.formattedData.value.views && selectedChart === 'views'"
                  ref="viewsChart"
                  type="line"
                  name="View data"
                  :hide-legend="true"
                  :data="analytics.formattedData.value.views.chart.data"
                  :labels="analytics.formattedData.value.views.chart.labels"
                  suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/><circle cx='12' cy='12' r='3'/></svg>"
                  :colors="
                    isUsingProjectColors
                      ? analytics.formattedData.value.views.chart.colors
                      : analytics.formattedData.value.views.chart.defaultColors
                  "
                />
                <Chart
                  v-if="analytics.formattedData.value.revenue && selectedChart === 'revenue'"
                  ref="revenueChart"
                  type="line"
                  name="Revenue data"
                  :hide-legend="true"
                  :data="analytics.formattedData.value.revenue.chart.data"
                  :labels="analytics.formattedData.value.revenue.chart.labels"
                  is-money
                  suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><line x1='12' y1='2' x2='12' y2='22'></line><path d='M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6'></path></svg>"
                  :colors="
                    isUsingProjectColors
                      ? analytics.formattedData.value.revenue.chart.colors
                      : analytics.formattedData.value.revenue.chart.defaultColors
                  "
                />
              </client-only>
            </div>
            <div class="legend">
              <div class="legend__items">
                <template v-for="project in selectedDataSetProjects" :key="project">
                  <button
                    v-tooltip="project.title"
                    :class="`legend__item button-base btn-transparent ${
                      !projectIsOnDisplay(project.id) ? 'btn-dimmed' : ''
                    }`"
                    @click="
                      () =>
                        projectIsOnDisplay(project.id) &&
                        analytics.validProjectIds.value.includes(project.id)
                          ? removeProjectFromDisplay(project.id)
                          : addProjectToDisplay(project.id)
                    "
                  >
                    <div
                      :style="{
                        '--color-brand': isUsingProjectColors
                          ? intToRgba(project.color, project.id, theme.active ?? undefined)
                          : getDefaultColor(project.id),
                      }"
                      class="legend__item__color"
                    ></div>
                    <div class="legend__item__text">{{ project.title }}</div>
                  </button>
                </template>
              </div>
            </div>
          </div>
        </div>
        <div class="country-data">
          <Card
            v-if="
              analytics.formattedData.value?.downloadsByCountry &&
              selectedChart === 'downloads' &&
              analytics.formattedData.value.downloadsByCountry.data.length > 0
            "
            class="country-downloads"
          >
            <label>
              <span class="label__title">Downloads by region</span>
            </label>
            <div class="country-values">
              <div
                v-for="[name, count] in analytics.formattedData.value.downloadsByCountry.data"
                :key="name"
                class="country-value"
              >
                <div class="country-flag-container">
                  <img
                    :src="
                      name.toLowerCase() === 'xx' || !name
                        ? 'https://cdn.modrinth.com/placeholder-banner.svg'
                        : countryCodeToFlag(name)
                    "
                    alt="Hidden country"
                    class="country-flag"
                  />
                </div>
                <div class="country-text">
                  <strong class="country-name"
                    ><template v-if="name.toLowerCase() === 'xx' || !name">Hidden</template>
                    <template v-else>{{ countryCodeToName(name) }}</template>
                  </strong>
                  <span class="data-point">{{ formatNumber(count) }}</span>
                </div>
                <div
                  v-tooltip="
                    formatPercent(count, analytics.formattedData.value.downloadsByCountry.sum)
                  "
                  class="percentage-bar"
                >
                  <span
                    :style="{
                      width: formatPercent(
                        count,
                        analytics.formattedData.value.downloadsByCountry.sum,
                      ),
                      backgroundColor: 'var(--color-brand)',
                    }"
                  ></span>
                </div>
              </div>
            </div>
          </Card>
          <Card
            v-if="
              analytics.formattedData.value?.viewsByCountry &&
              selectedChart === 'views' &&
              analytics.formattedData.value.viewsByCountry.data.length > 0
            "
            class="country-downloads"
          >
            <label>
              <span class="label__title">Page views by region</span>
            </label>
            <div class="country-values">
              <div
                v-for="[name, count] in analytics.formattedData.value.viewsByCountry.data"
                :key="name"
                class="country-value"
              >
                <div class="country-flag-container">
                  <img
                    :src="
                      name.toLowerCase() === 'xx' || !name
                        ? 'https://cdn.modrinth.com/placeholder-banner.svg'
                        : countryCodeToFlag(name)
                    "
                    alt="Hidden country"
                    class="country-flag"
                  />
                </div>
                <div class="country-text">
                  <strong class="country-name">
                    <template v-if="name.toLowerCase() === 'xx' || !name">Hidden</template>
                    <template v-else>{{ countryCodeToName(name) }}</template>
                  </strong>
                  <span class="data-point">{{ formatNumber(count) }}</span>
                </div>
                <div
                  v-tooltip="
                    `${
                      Math.round(
                        (count / analytics.formattedData.value.viewsByCountry.sum) * 10000,
                      ) / 100
                    }%`
                  "
                  class="percentage-bar"
                >
                  <span
                    :style="{
                      width: `${(count / analytics.formattedData.value.viewsByCountry.sum) * 100}%`,
                      backgroundColor: 'var(--color-blue)',
                    }"
                  ></span>
                </div>
              </div>
            </div>
          </Card>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Button, Card, DropdownSelect } from "@modrinth/ui";
import { formatMoney, formatNumber, formatCategoryHeader } from "@modrinth/utils";
import { UpdatedIcon, DownloadIcon } from "@modrinth/assets";
import dayjs from "dayjs";
import { computed } from "vue";

import { analyticsSetToCSVString, intToRgba } from "~/utils/analytics.js";

import { UiChartsCompactChart as CompactChart, UiChartsChart as Chart } from "#components";

import PaletteIcon from "~/assets/icons/palette.svg?component";

const router = useNativeRouter();
const theme = useTheme();

const props = withDefaults(
  defineProps<{
    projects?: any[];
    /**
     * @deprecated Use `ranges` instead
     */
    resoloutions?: Record<string, number>;
    ranges?: Record<number, [string, number] | string>;
    personal?: boolean;
  }>(),
  {
    projects: undefined,
    resoloutions: () => defaultResoloutions,
    ranges: () => defaultRanges,
    personal: false,
  },
);

const projects = ref(props.projects || []);

const selectableRanges = Object.entries(props.ranges).map(([duration, extra]) => ({
  label: typeof extra === "string" ? extra : extra[0],
  value: Number(duration),
  res: typeof extra === "string" ? Number(duration) : extra[1],
}));

// const selectedChart = ref('downloads')
const selectedChart = computed({
  get: () => {
    const id = (router.currentRoute.value.query?.chart as string | undefined) || "downloads";
    // if the id is anything but the 3 charts we have or undefined, throw an error
    if (!["downloads", "views", "revenue"].includes(id)) {
      throw new Error(`Unknown chart ${id}`);
    }
    return id;
  },
  set: (chart) => {
    router.push({
      query: {
        ...router.currentRoute.value.query,
        chart,
      },
    });
  },
});

// Chart refs
const downloadsChart = ref();
const viewsChart = ref();
const revenueChart = ref();
const tinyDownloadChart = ref();
const tinyViewChart = ref();
const tinyRevenueChart = ref();

const selectedDisplayProjects = ref(props.projects || []);

const removeProjectFromDisplay = (id: string) => {
  selectedDisplayProjects.value = selectedDisplayProjects.value.filter((p) => p.id !== id);
};

const addProjectToDisplay = (id: string) => {
  selectedDisplayProjects.value = [
    ...selectedDisplayProjects.value,
    props.projects?.find((p) => p.id === id),
  ].filter(Boolean);
};

const projectIsOnDisplay = (id: string) => {
  return selectedDisplayProjects.value?.some((p) => p.id === id) ?? false;
};

const resetCharts = () => {
  downloadsChart.value?.resetChart();
  viewsChart.value?.resetChart();
  revenueChart.value?.resetChart();

  tinyDownloadChart.value?.resetChart();
  tinyViewChart.value?.resetChart();
  tinyRevenueChart.value?.resetChart();
};

const isUsingProjectColors = computed({
  get: () => {
    return (
      router.currentRoute.value.query?.colors === "true" ||
      router.currentRoute.value.query?.colors === undefined
    );
  },
  set: (newValue) => {
    router.push({
      query: {
        ...router.currentRoute.value.query,
        colors: newValue ? "true" : "false",
      },
    });
  },
});

const analytics = useFetchAllAnalytics(
  resetCharts,
  projects,
  selectedDisplayProjects,
  props.personal,
);

const { startDate, endDate, timeRange, timeResolution } = analytics;

const selectedRange = computed({
  get: () => {
    return (
      selectableRanges.find((option) => option.value === timeRange.value) || {
        label: "Custom",
        value: timeRange.value,
      }
    );
  },
  set: (newRange: { label: string; value: number; res?: number }) => {
    timeRange.value = newRange.value;
    startDate.value = Date.now() - timeRange.value * 60 * 1000;
    endDate.value = Date.now();

    if (newRange?.res) {
      timeResolution.value = newRange.res;
    }
  },
});

const selectedDataSet = computed(() => {
  switch (selectedChart.value) {
    case "downloads":
      return analytics.totalData.value.downloads;
    case "views":
      return analytics.totalData.value.views;
    case "revenue":
      return analytics.totalData.value.revenue;
    default:
      throw new Error(`Unknown chart ${selectedChart.value}`);
  }
});
const selectedDataSetProjects = computed(() => {
  return selectedDataSet.value.projectIds
    .map((id) => props.projects?.find((p) => p?.id === id))
    .filter(Boolean);
});

const downloadSelectedSetAsCSV = () => {
  const selectedChartName = selectedChart.value;

  const csv = analyticsSetToCSVString(selectedDataSet.value);

  const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });

  const link = document.createElement("a");
  const url = URL.createObjectURL(blob);
  link.setAttribute("href", url);
  link.setAttribute("download", `${selectedChartName}-data.csv`);
  link.style.visibility = "hidden";
  document.body.appendChild(link);

  link.click();
};

const onDownloadSetAsCSV = useClientTry(async () => await downloadSelectedSetAsCSV());
const onToggleColors = () => {
  isUsingProjectColors.value = !isUsingProjectColors.value;
};
</script>

<script lang="ts">
const defaultResoloutions: Record<string, number> = {
  "5 minutes": 5,
  "30 minutes": 30,
  "An hour": 60,
  "12 hours": 720,
  "A day": 1440,
  "A week": 10080,
};

const defaultRanges: Record<number, [string, number] | string> = {
  30: ["Last 30 minutes", 1],
  60: ["Last hour", 5],
  720: ["Last 12 hours", 15],
  1440: ["Last day", 60],
  10080: ["Last week", 720],
  43200: ["Last month", 1440],
  129600: ["Last quarter", 10080],
  525600: ["Last year", 20160],
  1051200: ["Last two years", 40320],
};
</script>

<style scoped lang="scss">
.chart-controls {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--gap-md);

  .chart-controls__buttons {
    display: flex;
    flex-direction: row;
    gap: var(--gap-xs);

    * {
      width: auto;
      min-height: auto;
    }
  }
}

.chart-area {
  display: flex;
  flex-direction: row;
  gap: var(--gap-md);

  height: 100%;

  .chart {
    flex-grow: 1;
    flex-shrink: 1;

    display: flex;
    flex-direction: column;
    gap: var(--gap-md);
  }

  .legend {
    margin-top: 24px;
    overflow: hidden;

    max-width: 26ch;
    width: fit-content;

    .legend__items {
      display: flex;
      flex-direction: column;
      gap: var(--gap-xs);

      .legend__item {
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: var(--gap-xs);
        font-size: var(--font-size-sm);
        width: 100%;

        .legend__item__text {
          white-space: nowrap;
          text-overflow: ellipsis;
        }

        .legend__item__color {
          height: var(--font-size-xs);
          width: var(--font-size-xs);
          border-radius: var(--radius-sm);
          background-color: var(--color-brand);

          flex-grow: 0;
          flex-shrink: 0;
        }
      }
    }
  }
}

.btn-transparent {
  background-color: transparent;
  border: none;
  cursor: pointer;

  color: var(--text-color);
  font-weight: var(--font-weight-regular);
}

.btn-dimmed {
  opacity: 0.5;
}

.chart-button-base {
  overflow: hidden;
}

.chart-button-base__selected {
  color: var(--color-contrast);
  background-color: var(--color-brand-highlight);
  box-shadow:
    inset 0 0 0 transparent,
    0 0 0 2px var(--color-brand);

  &:hover {
    background-color: var(--color-brand-highlight);
  }
}

.graphs {
  // Pages clip so we need to add a margin
  margin-left: 0.25rem;
  margin-top: 0.25rem;

  display: flex;
  flex-direction: column;

  .graphs__vertical-bar {
    flex-grow: 0;
    flex-shrink: 0;
    gap: 0.75rem;
    display: flex;
    margin-right: 0.1rem;
  }
}

.country-flag-container {
  width: 40px;
  height: 27px;

  display: flex;
  justify-content: center;
  align-items: center;

  overflow: hidden;

  border: 1px solid var(--color-divider);
  border-radius: var(--radius-xs);
}

.country-flag {
  object-fit: cover;

  min-width: 100%;
  min-height: 100%;
}

.spark-data {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--gap-md);
}

.country-data {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap-md);
}

.country-values {
  display: flex;
  flex-direction: column;
  background-color: var(--color-bg);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-button-bg);
  gap: var(--gap-md);
  padding: var(--gap-md);
  margin-top: var(--gap-md);
  overflow-y: auto;
  max-height: 24rem;
}

.country-value {
  display: grid;
  grid-template-areas: "flag text bar";
  grid-template-columns: auto 1fr 10rem;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: var(--gap-sm);

  .country-text {
    grid-area: text;
    display: flex;
    flex-direction: column;
    gap: var(--gap-xs);
  }
  .percentage-bar {
    grid-area: bar;
    width: 100%;
    height: 1rem;
    background-color: var(--color-raised-bg);
    border: 1px solid var(--color-button-bg);
    border-radius: var(--radius-sm);
    overflow: hidden;
    span {
      display: block;
      height: 100%;
    }
  }
}

@media (max-width: 768px) {
  .chart-area {
    flex-direction: column;
    gap: var(--gap-md);
  }

  .chart-controls {
    flex-direction: column;
    gap: var(--gap-md);
  }

  .chart {
    flex-direction: column;
    gap: var(--gap-md);
  }

  .legend {
    margin-top: 0px;
    width: 100%;
    max-width: 100%;
  }

  .graphs {
    margin-left: 0px;
    margin-top: 0px;

    .graphs__vertical-bar {
      flex-direction: column;
      gap: 0;
      margin-right: 0px;
    }
  }

  .country-data {
    display: block;
  }

  .country-value {
    grid-template-columns: auto 1fr 5rem;
  }
}
</style>
