<template>
  <section class="chart-card card-glow">
    <div class="card-header">
      <div>
        <h2 class="card-title">Historique systeme</h2>
        <p class="card-copy">CPU et RAM avec cache journalier local</p>
      </div>

      <div class="controls">
        <div class="toggle-group" role="group" aria-label="Metriques affichees">
          <button
            v-for="option in options"
            :key="option.value"
            type="button"
            class="toggle-pill"
            :class="{ 'toggle-pill-active': isMetricActive(option.value) }"
            @click="toggleMetric(option.value)"
          >
            <span class="toggle-dot" :style="{ backgroundColor: option.color }" />
            <span>{{ option.label }}</span>
          </button>
        </div>

        <div class="history-toolbar">
          <label class="window-select">
            <span>Fenetre</span>
            <select v-model="selectedWindow">
              <option
                v-for="windowOption in windowOptions"
                :key="windowOption.value"
                :value="windowOption.value"
              >
                {{ windowOption.label }}
              </option>
            </select>
          </label>

          <button type="button" class="clear-btn" @click="clearHistory">
            Vider le cache
          </button>
        </div>
      </div>
    </div>

    <div class="chart-shell">
      <template v-if="loading && visibleHistory.length === 0">
        <div class="chart-skeleton animate-shimmer" />
      </template>
      <template v-else>
        <div class="chart-meta">
          <div>
            <span class="meta-label">Echelle</span>
            <strong class="meta-value">{{ scaleLabel }}</strong>
          </div>
          <div>
            <span class="meta-label">Periode</span>
            <strong class="meta-value">{{ activeWindowLabel }}</strong>
          </div>
          <div
            v-for="option in displayedOptions"
            :key="option.value"
            class="meta-metric"
          >
            <span class="meta-label">{{ option.label }}</span>
            <strong class="meta-value" :style="{ color: option.color }">
              {{ formatValue(currentMetricValue(option.value)) }}
            </strong>
            <span class="meta-subvalue">Pic {{ formatValue(peakMetricValue(option.value)) }}</span>
          </div>
        </div>

        <svg
          class="chart-svg"
          viewBox="0 0 960 320"
          preserveAspectRatio="none"
          aria-label="Graphique des ressources"
        >
          <line
            :x1="chartLeft"
            :y1="chartBottom"
            :x2="chartRight"
            :y2="chartBottom"
            class="axis-line"
          />
          <line
            :x1="chartLeft"
            :y1="chartTop"
            :x2="chartLeft"
            :y2="chartBottom"
            class="axis-line"
          />
          <line
            v-for="line in yAxisTicks"
            :key="`grid-${line.y}`"
            :x1="chartLeft"
            :y1="line.y"
            :x2="chartRight"
            :y2="line.y"
            class="grid-line"
          />
          <text
            v-for="line in yAxisTicks"
            :key="`y-label-${line.y}`"
            :x="chartLeft - 12"
            :y="line.y + 4"
            class="axis-label axis-label-y"
          >
            {{ line.label }}
          </text>
          <line
            v-for="tick in xAxisTicks"
            :key="`x-grid-${tick.x}`"
            :x1="tick.x"
            :y1="chartTop"
            :x2="tick.x"
            :y2="chartBottom"
            class="grid-line grid-line-vertical"
          />
          <text
            v-for="tick in xAxisTicks"
            :key="`x-label-${tick.x}`"
            :x="tick.x"
            :y="304"
            class="axis-label axis-label-x"
          >
            {{ tick.label }}
          </text>
          <polyline
            v-for="option in displayedOptions"
            :key="option.value"
            :points="polylinePoints(option.value)"
            class="chart-line"
            :style="{ stroke: option.color }"
          />
        </svg>
      </template>
    </div>
  </section>
</template>

<script setup lang="ts">
import type { SystemMetrics } from "~/types/system"

type MetricKey = "cpu" | "ram"
type WindowKey = "day" | "hour" | "5m" | "1m" | "30s"

type HistoryPoint = {
  sampledAt: number
  cpu: number
  ram: number
}

const HISTORY_STORAGE_KEY = "supervisor-system-history"

const props = defineProps<{
  metrics: SystemMetrics | null
  loading: boolean
}>()

const activeMetrics = ref<MetricKey[]>(["cpu", "ram"])
const selectedWindow = ref<WindowKey>("hour")
const history = ref<HistoryPoint[]>([])

const options: Array<{ value: MetricKey; label: string; color: string }> = [
  { value: "cpu", label: "CPU", color: "#5aa9ff" },
  { value: "ram", label: "RAM", color: "#31c48d" }
]

const windowOptions: Array<{ value: WindowKey; label: string; durationMs: number | null }> = [
  { value: "day", label: "Journee", durationMs: null },
  { value: "hour", label: "1 h", durationMs: 60 * 60 * 1000 },
  { value: "5m", label: "5 min", durationMs: 5 * 60 * 1000 },
  { value: "1m", label: "1 min", durationMs: 60 * 1000 },
  { value: "30s", label: "30 s", durationMs: 30 * 1000 }
]

const getStartOfToday = (timestamp: number) => {
  const date = new Date(timestamp)
  date.setHours(0, 0, 0, 0)
  return date.getTime()
}

const normalizeHistory = (points: HistoryPoint[]) => {
  if (points.length === 0) {
    return []
  }

  const startOfToday = getStartOfToday(Date.now())

  return points
    .filter((point) => point.sampledAt >= startOfToday)
    .sort((left, right) => left.sampledAt - right.sampledAt)
}

const persistHistory = () => {
  if (!import.meta.client) {
    return
  }

  localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(history.value))
}

const appendHistoryPoint = (metrics: SystemMetrics) => {
  const sampledAt = new Date(metrics.sampledAt).getTime()

  if (!Number.isFinite(sampledAt)) {
    return
  }

  const nextPoint: HistoryPoint = {
    sampledAt,
    cpu: metrics.cpuPercent,
    ram: metrics.memoryPercent
  }

  const previousPoint = history.value.at(-1)
  const nextHistory = normalizeHistory(
    previousPoint?.sampledAt === nextPoint.sampledAt
      ? [...history.value.slice(0, -1), nextPoint]
      : [...history.value, nextPoint]
  )

  history.value = nextHistory
  persistHistory()
}

const clearHistory = () => {
  history.value = []

  if (!import.meta.client) {
    return
  }

  localStorage.removeItem(HISTORY_STORAGE_KEY)
}

onMounted(() => {
  if (!import.meta.client) {
    return
  }

  try {
    const rawHistory = localStorage.getItem(HISTORY_STORAGE_KEY)

    if (!rawHistory) {
      return
    }

    const parsedHistory = JSON.parse(rawHistory) as HistoryPoint[]
    history.value = normalizeHistory(
      parsedHistory.filter((point) => {
        return (
          point &&
          Number.isFinite(point.sampledAt) &&
          Number.isFinite(point.cpu) &&
          Number.isFinite(point.ram)
        )
      })
    )
    persistHistory()
  } catch {
    localStorage.removeItem(HISTORY_STORAGE_KEY)
  }
})

watch(
  () => props.metrics?.sampledAt,
  () => {
    if (!props.metrics) {
      return
    }

    appendHistoryPoint(props.metrics)
  },
  { immediate: true }
)

const activeWindow = computed(() => {
  return windowOptions.find((option) => option.value === selectedWindow.value) || windowOptions[0]
})

const displayedOptions = computed(() => {
  return options.filter((option) => activeMetrics.value.includes(option.value))
})

const visibleHistory = computed(() => {
  if (activeWindow.value.durationMs === null) {
    return history.value
  }

  const minTimestamp = Date.now() - activeWindow.value.durationMs
  return history.value.filter((point) => point.sampledAt >= minTimestamp)
})

const scaleMax = computed(() => 100)

const formatValue = (value: number) => `${Math.round(value)}%`

const scaleLabel = computed(() => {
  return formatValue(scaleMax.value)
})

const activeWindowLabel = computed(() => activeWindow.value.label)

const isMetricActive = (metric: MetricKey) => activeMetrics.value.includes(metric)

const toggleMetric = (metric: MetricKey) => {
  if (isMetricActive(metric)) {
    activeMetrics.value = activeMetrics.value.filter((value) => value !== metric)
    return
  }

  activeMetrics.value = [...activeMetrics.value, metric]
}

const currentMetricValue = (metric: MetricKey) => {
  return visibleHistory.value.at(-1)?.[metric] ?? 0
}

const peakMetricValue = (metric: MetricKey) => {
  return visibleHistory.value.reduce((max, point) => Math.max(max, point[metric]), 0)
}

const chartLeft = 72
const chartRight = 936
const chartTop = 24
const chartBottom = 280
const chartWidth = chartRight - chartLeft
const chartHeight = chartBottom - chartTop

const yAxisTicks = computed(() => {
  const steps = 4

  return Array.from({ length: steps + 1 }, (_, index) => {
    const ratio = index / steps
    const value = scaleMax.value * (1 - ratio)
    const y = chartTop + chartHeight * ratio

    return {
      y,
      label: formatValue(value)
    }
  })
})

const formatTimeLabel = (timestamp: number) => {
  return new Intl.DateTimeFormat("fr-FR", {
    hour: "2-digit",
    minute: "2-digit",
    second: activeWindow.value.durationMs !== null && activeWindow.value.durationMs <= 5 * 60 * 1000
      ? "2-digit"
      : undefined
  }).format(timestamp)
}

const xAxisTicks = computed(() => {
  if (visibleHistory.value.length === 0) {
    return []
  }

  if (visibleHistory.value.length === 1) {
    return [
      {
        x: chartLeft,
        label: formatTimeLabel(visibleHistory.value[0].sampledAt)
      }
    ]
  }

  const steps = 3
  const firstTimestamp = visibleHistory.value[0].sampledAt
  const lastTimestamp = visibleHistory.value.at(-1)?.sampledAt || firstTimestamp
  const span = Math.max(1, lastTimestamp - firstTimestamp)

  return Array.from({ length: steps + 1 }, (_, index) => {
    const ratio = index / steps
    const targetTimestamp = firstTimestamp + span * ratio
    const closestPoint = visibleHistory.value.reduce((closest, point) => {
      const currentDistance = Math.abs(point.sampledAt - targetTimestamp)
      const closestDistance = Math.abs(closest.sampledAt - targetTimestamp)
      return currentDistance < closestDistance ? point : closest
    }, visibleHistory.value[0])

    return {
      x: chartLeft + chartWidth * ratio,
      label: formatTimeLabel(closestPoint.sampledAt)
    }
  })
})

const polylinePoints = (metric: MetricKey) => {
  const points = visibleHistory.value.map((point) => point[metric])

  if (points.length === 0) {
    return `${chartLeft},${chartBottom}`
  }

  if (points.length === 1) {
    const normalizedValue = points[0] / scaleMax.value
    const y = chartBottom - normalizedValue * chartHeight
    return `${chartLeft},${y} ${chartRight},${y}`
  }

  return points
    .map((value, index) => {
      const x = chartLeft + (index / (points.length - 1)) * chartWidth
      const normalizedValue = scaleMax.value > 0 ? value / scaleMax.value : 0
      const y = chartBottom - normalizedValue * chartHeight

      return `${x},${Math.max(chartTop, Math.min(chartBottom, y))}`
    })
    .join(" ")
}
</script>

<style scoped>
.chart-card {
  margin-top: 1.5rem;
  background: rgb(var(--m-secondary));
  border-radius: 12px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.card-title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: rgb(var(--m-text));
}

.card-copy {
  margin-top: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: rgb(var(--m-muted));
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.controls {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.75rem;
}

.toggle-group {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  flex-wrap: wrap;
}

.toggle-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  border-radius: 999px;
  border: 1px solid rgb(var(--m-accent) / 0.1);
  background: rgb(var(--m-tertiary));
  padding: 0.55rem 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: rgb(var(--m-muted));
  cursor: pointer;
  appearance: none;
  transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}

.toggle-pill-active {
  border-color: rgb(var(--m-accent) / 0.28);
  color: rgb(var(--m-text));
  transform: translateY(-1px);
}

.toggle-dot {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  flex-shrink: 0;
}

.history-toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.window-select {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgb(var(--m-muted));
}

.window-select select {
  border-radius: 999px;
  border: 1px solid rgb(var(--m-accent) / 0.14);
  background: rgb(var(--m-tertiary));
  padding: 0.55rem 0.8rem;
  font: inherit;
  color: rgb(var(--m-text));
}

.clear-btn {
  border-radius: 999px;
  border: 1px solid rgb(var(--m-error) / 0.18);
  background: rgb(var(--m-error) / 0.08);
  padding: 0.55rem 0.85rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgb(var(--m-error));
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
}

.clear-btn:hover {
  border-color: rgb(var(--m-error) / 0.32);
  background: rgb(var(--m-error) / 0.14);
}

.chart-shell {
  border-radius: 12px;
  padding: 1rem;
  background:
    linear-gradient(180deg, rgb(var(--m-tertiary)) 0%, rgb(var(--m-secondary)) 100%);
  border: 1px solid rgb(var(--m-accent) / 0.08);
}

.chart-meta {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.meta-metric {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.meta-label {
  display: block;
  margin-bottom: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: rgb(var(--m-muted));
}

.meta-value {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 700;
  color: rgb(var(--m-text));
}

.meta-subvalue {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgb(var(--m-muted));
}

.chart-svg {
  width: 100%;
  height: 320px;
  display: block;
}

.axis-line {
  stroke: rgb(var(--m-border) / 0.55);
  stroke-width: 1.5;
}

.grid-line {
  stroke: rgb(var(--m-border) / 0.35);
  stroke-width: 1;
  stroke-dasharray: 6 10;
}

.grid-line-vertical {
  stroke-dasharray: 4 12;
}

.axis-label {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: rgb(var(--m-muted));
}

.axis-label-y {
  text-anchor: end;
}

.axis-label-x {
  text-anchor: middle;
}

.chart-line {
  fill: none;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chart-skeleton {
  width: 100%;
  height: 320px;
  border-radius: 12px;
}

@media (max-width: 820px) {
  .controls {
    width: 100%;
    align-items: stretch;
  }

  .history-toolbar {
    justify-content: space-between;
  }

  .chart-meta {
    grid-template-columns: 1fr;
  }
}
</style>
