<script setup lang="ts">
import { computed, PropType } from "vue";

const props = defineProps({
  points: {
    type: Array as PropType<Array<{ t: number | string; v: number }>>,
    default: () => [],
  },
  width: {
    type: Number,
    default: 280,
  },
  height: {
    type: Number,
    default: 140,
  },
  yTicks: {
    type: Number,
    default: 4,
  },
  xTicks: {
    type: Number,
    default: 4,
  },
  formatY: {
    type: Function as PropType<(value: number) => string>,
    default: (value: number) => new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", maximumFractionDigits: 0 }).format(value),
  },
  formatX: {
    type: Function as PropType<(value: number | string) => string>,
    default: (value: number | string) => String(value),
  },
});

const margins = {
  left: 44,
  right: 8,
  top: 8,
  bottom: 22,
};

const validPoints = computed(() =>
  props.points
    .map((item) => {
      const value = Number(item.v);
      return {
        t: item.t,
        v: Number.isFinite(value) ? value : NaN,
      };
    })
    .filter((item) => Number.isFinite(item.v))
);

const hasTimestamp = computed(() => {
  return validPoints.value.every((item) => {
    if (typeof item.t === "number") {
      return true;
    }
    const parsed = Date.parse(String(item.t));
    return !Number.isNaN(parsed);
  });
});

const timestamps = computed(() =>
  validPoints.value.map((item) => {
    if (typeof item.t === "number") {
      return item.t;
    }
    const parsed = Date.parse(String(item.t));
    return Number.isNaN(parsed) ? null : parsed;
  })
);

const yBounds = computed(() => {
  if (!validPoints.value.length) {
    return { min: 0, max: 0 };
  }
  const values = validPoints.value.map((item) => item.v);
  const rawMin = Math.min(...values);
  const rawMax = Math.max(...values);
  const delta = Math.max(rawMax - rawMin, 1);
  const pad = delta * 0.05;
  return {
    min: rawMin - pad,
    max: rawMax + pad,
  };
});

const chartDimensions = computed(() => ({
  width: props.width - margins.left - margins.right,
  height: props.height - margins.top - margins.bottom,
}));

const chartPoints = computed(() => {
  const points = validPoints.value;
  if (points.length === 0) {
    return [];
  }
  const { min, max } = yBounds.value;
  const delta = max - min || 1;
  const { width: chartWidth, height: chartHeight } = chartDimensions.value;
  const timeRange =
    hasTimestamp.value && timestamps.value.some((t) => t !== null)
      ? (timestamps.value as number[]).reduce(
          (acc, cur) => {
            if (cur === null) {
              return acc;
            }
            acc.min = acc.min === null ? cur : Math.min(acc.min, cur);
            acc.max = acc.max === null ? cur : Math.max(acc.max, cur);
            return acc;
          },
          { min: null as number | null, max: null as number | null }
        )
      : { min: null, max: null };
  const times = (timestamps.value as Array<number | null>).map((value, index) => {
    if (hasTimestamp.value && value !== null && timeRange.min !== null && timeRange.max !== null) {
      const range = Math.max(timeRange.max - timeRange.min, 1);
      return (value - timeRange.min) / range;
    }
    return points.length > 1 ? index / (points.length - 1) : 0;
  });

  return points.map((point, index) => {
    const x = margins.left + chartWidth * times[index];
    const normalizedY = 1 - (point.v - min) / delta;
    const y = margins.top + chartHeight * normalizedY;
    return { x, y, value: point.v, raw: point.t };
  });
});

const hasPoints = computed(() => chartPoints.value.length > 0);

const linePoints = computed(() => {
  if (!chartPoints.value.length) {
    return [];
  }
  if (chartPoints.value.length === 1) {
    const point = chartPoints.value[0];
    const endX = margins.left + chartDimensions.value.width;
    return [
      { x: margins.left, y: point.y },
      { x: endX, y: point.y },
    ];
  }
  return chartPoints.value;
});

const polylinePoints = computed(() => linePoints.value.map((point) => `${point.x},${point.y}`).join(" "));

const yTickValues = computed(() => {
  const count = Math.max(2, props.yTicks);
  const { min, max } = yBounds.value;
  const step = (max - min) / (count - 1 || 1);
  return Array.from({ length: count }, (_, index) => ({
    value: min + step * index,
    position: margins.top + chartDimensions.value.height * (1 - (min + step * index - min) / (max - min || 1)),
  }));
});

const xTickIndices = computed(() => {
  const points = chartPoints.value;
  const count = Math.max(2, Math.min(points.length, props.xTicks));
  if (!points.length) {
    return [];
  }
  return Array.from({ length: count }, (_, index) => {
    const idx = Math.round((points.length - 1) * (index / (count - 1 || 1)));
    return points[idx];
  });
});

const xLabels = computed(() => {
  return xTickIndices.value.map((point) => ({
    label: props.formatX(point.raw),
    x: point.x,
  }));
});

const formattedYTicks = computed(() =>
  yTickValues.value.map((tick) => ({
    label: props.formatY(tick.value),
    y: tick.position,
  }))
);

const placeholderLabel = computed(() => "");
</script>

<template>
  <div class="mini-line-chart-wrapper">
    <svg
      v-if="hasPoints"
      :width="width"
      :height="height"
      :viewBox="`0 0 ${width} ${height}`"
      role="presentation"
      aria-hidden="true"
    >
      <line
        :x1="margins.left"
        :x2="margins.left"
        :y1="margins.top"
        :y2="margins.top + chartDimensions.height"
        stroke="currentColor"
        stroke-width="1"
        opacity="0.35"
      />
      <line
        :x1="margins.left"
        :x2="margins.left + chartDimensions.width"
        :y1="margins.top + chartDimensions.height"
        :y2="margins.top + chartDimensions.height"
        stroke="currentColor"
        stroke-width="1"
        opacity="0.35"
      />
      <g v-for="tick in formattedYTicks" :key="tick.label">
        <line
          :x1="margins.left - 6"
          :x2="margins.left"
          :y1="tick.y"
          :y2="tick.y"
          stroke="currentColor"
          stroke-width="1"
          opacity="0.35"
        />
        <text
          :x="margins.left - 10"
          :y="tick.y + 4"
          class="text-[10px]"
          text-anchor="end"
          :opacity="0.65"
        >
          {{ tick.label }}
        </text>
      </g>
      <g v-for="label in xLabels" :key="label.label">
        <line
          :x1="label.x"
          :x2="label.x"
          :y1="margins.top + chartDimensions.height"
          :y2="margins.top + chartDimensions.height + 6"
          stroke="currentColor"
          stroke-width="1"
          opacity="0.35"
        />
        <text
          :x="label.x"
          :y="height - 4"
          class="text-[10px]"
          text-anchor="middle"
          :opacity="0.65"
        >
          {{ label.label }}
        </text>
      </g>
      <polyline
        :points="polylinePoints"
        stroke="currentColor"
        fill="none"
        stroke-width="2"
      />
      <circle
        v-for="(point, index) in chartPoints"
        :key="`${point.x}-${point.y}-${index}`"
        :cx="point.x"
        :cy="point.y"
        r="2"
        stroke="currentColor"
        stroke-width="1"
        fill="currentColor"
        :class="{ 'mini-line-chart__point--last': index === chartPoints.length - 1 }"
      />
    </svg>
    <div v-else class="history-placeholder" aria-hidden="true">
      {{ placeholderLabel }}
    </div>
  </div>
</template>
