<script setup lang="ts">
/**
 * DashboardView — Single-server cockpit wired entirely to real data.
 *
 * Architecture:
 *   - useServerStore  → connection + config + live stats (WebSocket updateStats)
 *   - useApi          → /analytics/timeseries for 24h player history (PlayersChart)
 *   - useGameProfile  → per-game labels/terminology (defaults to 'rust' today)
 *   - useWebSocket    → subscribes to console_output and server_stats events
 *
 * Empty states:
 *   - No connection record  → "No server connected" EmptyState with CTA to /server
 *   - Connection exists but stats absent → meters show '—', chart shows awaiting telemetry
 *   - No upcoming wipe schedules → honest empty state in the wipes panel
 *
 * No fabricated data anywhere in this file.
 * The fleet/multi-server view has been removed — the current backend is
 * single-server-per-license. When the backend supports multiple servers per
 * license, restore a fleet tab wired to real data.
 */
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useServerStore } from '@/stores/server'
import { useWipeStore } from '@/stores/wipe'
import { useApi } from '@/composables/useApi'
import { useWebSocket, type WebSocketMessage } from '@/composables/useWebSocket'
import { useGameProfile } from '@/config/gameProfiles'
import { useThemeGame } from '@/composables/useThemeGame'
import Panel from '@/components/ds/data/Panel.vue'
import StatCard from '@/components/ds/data/StatCard.vue'
import ConsoleLineDS from '@/components/ds/data/ConsoleLine.vue'
import ResourceMeter from '@/components/ds/data/ResourceMeter.vue'
import PlayersChart from '@/components/ds/data/PlayersChart.vue'
import Badge from '@/components/ds/core/Badge.vue'
import Button from '@/components/ds/core/Button.vue'
import Input from '@/components/ds/forms/Input.vue'
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
import type { TimeseriesData, WipeSchedule } from '@/types'
import { safeDate } from '@/utils/formatters'

// ---------------------------------------------------------------------------
// Stores / composables
// ---------------------------------------------------------------------------
const server = useServerStore()
const wipeStore = useWipeStore()
const router = useRouter()
const api = useApi()
const { activeGame } = useThemeGame()

// Profile follows the GameSwitcher selection. 'all' falls back to rust (neutral house skin).
// When the backend adds a `game` field on licenses, swap activeGame for server.config?.game.
const profile = computed(() => {
  const game = activeGame.value === 'all' ? 'rust' : activeGame.value
  return useGameProfile(game)
})

// ---------------------------------------------------------------------------
// Derived server state — all real, no fallbacks to fabricated values
// ---------------------------------------------------------------------------

const hasConnection = computed(() => server.connection !== null)
const isConnected = computed(() => server.connection?.connection_status === 'connected')

const soloName = computed(() => server.config?.server_name ?? null)

const soloPlayers = computed(() => server.stats?.player_count ?? null)
const soloMaxPlayers = computed(() => server.stats?.max_players ?? server.config?.max_players ?? null)
const soloFps = computed(() => server.stats?.fps ?? null)

// Memory: store gives memory_usage_mb; max must come from agent telemetry.
// We do NOT hard-code a "representative" max — show raw MB and no percentage
// until the agent reports a known max.
const soloRamMb = computed(() => server.stats?.memory_usage_mb ?? null)
const soloRamPct = computed(() => {
  // ServerStats has no ram_max field — we cannot compute a real percentage.
  // Return null; ResourceMeter and StatCard will show '—'.
  return null
})
const soloRamSub = computed(() => {
  const mb = soloRamMb.value
  if (mb === null) return null
  return `${(mb / 1024).toFixed(1)} GB used`
})

// CPU: not in ServerStats today. Show null — never fabricate.
const soloCpu = computed(() => null as number | null)

const soloStatus = computed<'online' | 'offline' | 'starting'>(() => {
  const cs = server.connection?.connection_status
  if (cs === 'connected') return 'online'
  if (cs === 'degraded') return 'starting'
  return 'offline'
})
const soloStatusTone = computed<'online' | 'offline' | 'warn'>(() => {
  if (soloStatus.value === 'online') return 'online'
  if (soloStatus.value === 'starting') return 'warn'
  return 'offline'
})
const soloStatusLabel = computed(() => {
  if (soloStatus.value === 'online') return 'Online'
  if (soloStatus.value === 'starting') return 'Degraded'
  return 'Offline'
})

const soloIp = computed(() => {
  const ip = server.connection?.server_ip
  const port = server.connection?.game_port ?? server.connection?.server_port
  if (ip && port) return `${ip}:${port}`
  if (ip) return ip
  return null
})

const soloUptime = computed(() => {
  const sec = server.stats?.uptime_seconds ?? 0
  if (sec === 0) return null
  const d = Math.floor(sec / 86400)
  const h = Math.floor((sec % 86400) / 3600)
  if (d > 0) return `${d}d ${h}h`
  return `${h}h`
})

// ---------------------------------------------------------------------------
// Players chart — real 24h timeseries from /analytics/timeseries
// ---------------------------------------------------------------------------
const chartData = ref<number[] | null>(null)
const chartLoading = ref(false)

async function loadChartData() {
  chartLoading.value = true
  try {
    const ts = await api.get<TimeseriesData>('/analytics/timeseries?range=24&granularity=hourly')
    chartData.value = ts.player_count.length > 0 ? ts.player_count : null
  } catch {
    // API unavailable or no data yet — chart will show "awaiting telemetry"
    chartData.value = null
  } finally {
    chartLoading.value = false
  }
}

// ---------------------------------------------------------------------------
// Wipe schedules — real data from wipeStore
// ---------------------------------------------------------------------------
const nextWipe = computed<WipeSchedule | null>(() => {
  const schedules = wipeStore.schedules.filter((s) => s.is_active && s.next_scheduled_run)
  if (schedules.length === 0) return null
  return schedules.slice().sort((a, b) => {
    const at = a.next_scheduled_run ? new Date(a.next_scheduled_run).getTime() : Infinity
    const bt = b.next_scheduled_run ? new Date(b.next_scheduled_run).getTime() : Infinity
    return at - bt
  })[0] ?? null
})

const nextWipeLabel = computed(() => {
  const w = nextWipe.value
  if (!w?.next_scheduled_run) return null
  return safeDate(w.next_scheduled_run)
})

const nextWipeType = computed(() => {
  const w = nextWipe.value
  if (!w) return null
  const t = w.wipe_type
  if (t === 'full') return `Full ${profile.value.terminology.reset}`
  if (t === 'blueprint') return 'Blueprint wipe'
  return `Map ${profile.value.terminology.reset}`
})

// ---------------------------------------------------------------------------
// Console lines — real WebSocket events only
// ---------------------------------------------------------------------------
interface ConsoleLine {
  time: string
  level: 'cmd' | 'chat' | 'info' | 'warn' | 'error' | 'connect' | 'kill'
  who?: string
  msg: string
}

const consoleLines = ref<ConsoleLine[]>([])
const MAX_CONSOLE_LINES = 100

function now(): string {
  return new Date().toLocaleTimeString('en-US', { hour12: false })
}

function handleWsMessage(msg: WebSocketMessage) {
  if (msg.type !== 'event') return

  // Live server stats
  if (msg.event === 'server_stats' && msg.data) {
    server.updateStats(msg.data)
    return
  }

  // Console output lines
  if (msg.event === 'console_output') {
    const text = msg.data?.line ?? msg.data?.output ?? msg.raw ?? ''
    if (!text) return
    consoleLines.value.push({
      time: now(),
      level: 'info',
      msg: String(text),
    })
    if (consoleLines.value.length > MAX_CONSOLE_LINES) {
      consoleLines.value.splice(0, consoleLines.value.length - MAX_CONSOLE_LINES)
    }
  }
}

// ---------------------------------------------------------------------------
// Console input
// ---------------------------------------------------------------------------
const consoleInput = ref('')

function sendConsoleCommand() {
  const cmd = consoleInput.value.trim()
  if (!cmd) return
  consoleLines.value.push({ time: now(), level: 'cmd', who: 'admin', msg: cmd })
  server.sendCommand(cmd).catch(() => {})
  consoleInput.value = ''
}

// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
let unsubscribe: (() => void) | null = null

onMounted(async () => {
  await server.fetchServer()
  await wipeStore.fetchSchedules()
  await loadChartData()

  const ws = useWebSocket()
  unsubscribe = ws.subscribe(handleWsMessage)
})

onUnmounted(() => {
  unsubscribe?.()
})

// Navigation helpers
function navConsole() { router.push('/console') }
function navWipes() { router.push('/wipes') }
function navServer() { router.push('/server') }
</script>

<template>
  <div class="dash">

    <!-- ===== NO CONNECTION: honest empty state ===== -->
    <template v-if="!server.isLoading && !hasConnection">
      <div class="page__head">
        <div>
          <div class="t-eyebrow">Dashboard</div>
          <h1 class="page__title">Server cockpit</h1>
        </div>
      </div>
      <Panel>
        <EmptyState
          icon="server"
          title="No server connected"
          description="Install re-Agent on your host machine to begin managing your server from Catalyst."
        >
          <template #action>
            <Button icon="server" @click="navServer">Set up server</Button>
          </template>
        </EmptyState>
      </Panel>
    </template>

    <!-- ===== SERVER COCKPIT ===== -->
    <template v-else-if="hasConnection">

      <!-- Page head -->
      <div class="page__head">
        <div class="solo-id">
          <div class="solo-id__chip">
            <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/>
              <line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>
            </svg>
          </div>
          <div>
            <div class="solo-id__name">
              {{ soloName ?? 'Server' }}
              <Badge :tone="soloStatusTone" :dot="true" :pulse="soloStatus === 'online'">{{ soloStatusLabel }}</Badge>
            </div>
            <div class="solo-id__meta">
              <template v-if="soloIp">{{ soloIp }}</template>
              <template v-else>No IP registered</template>
              <template v-if="soloUptime"> · up {{ soloUptime }}</template>
            </div>
          </div>
        </div>
        <div class="page__actions">
          <Button variant="secondary" size="sm" icon="refresh-cw" @click="server.restartServer()">Restart</Button>
          <Button variant="danger-soft" size="sm" icon="power" @click="server.stopServer()">Stop</Button>
          <Button size="sm" icon="terminal" @click="navConsole">Console</Button>
        </div>
      </div>

      <!-- KPIs — game profile drives stat labels; null values show '—' -->
      <div class="dash__kpis">
        <StatCard
          icon="users"
          :label="(profile.statFields[0] ?? 'Players') + ' online'"
          :value="soloPlayers !== null ? String(soloPlayers) : '—'"
          :unit="soloMaxPlayers !== null ? '/' + soloMaxPlayers : ''"
          note="live via agent"
        />
        <StatCard
          icon="cpu"
          label="CPU"
          :value="soloCpu !== null ? String(soloCpu) : '—'"
          :unit="soloCpu !== null ? '%' : ''"
          note="agent telemetry"
        />
        <StatCard
          icon="memory-stick"
          label="Memory"
          :value="soloRamMb !== null ? (soloRamMb / 1024).toFixed(1) : '—'"
          :unit="soloRamMb !== null ? 'GB' : ''"
          :note="soloRamSub ?? 'agent telemetry'"
        />
        <StatCard
          icon="gauge"
          label="Server FPS"
          :value="soloFps !== null ? String(soloFps) : '—'"
          :unit="soloFps !== null ? 'fps' : ''"
          note="live via agent"
        />
      </div>

      <!-- Main grid -->
      <div class="dash__grid">

        <!-- Left column -->
        <div class="dash__col">

          <!-- Players chart — real 24h data or honest empty state -->
          <Panel
            title="Players online"
            :subtitle="(soloName ?? 'Server') + ' · last 24 hours'"
          >
            <div v-if="chartLoading" class="chart-loading">Loading telemetry…</div>
            <PlayersChart
              v-else
              :height="196"
              :max="soloMaxPlayers ?? 200"
              :data="chartData ?? undefined"
            />
          </Panel>

          <!-- Console — real WebSocket lines only -->
          <Panel :flush-body="true" title="Console">
            <template #actions>
              <Badge
                :tone="isConnected ? 'online' : 'offline'"
                :dot="true"
                :pulse="isConnected"
              >{{ isConnected ? 'Live' : 'Disconnected' }}</Badge>
            </template>

            <div class="feed feed--solo">
              <template v-if="consoleLines.length > 0">
                <ConsoleLineDS
                  v-for="(line, i) in consoleLines"
                  :key="i"
                  :time="line.time"
                  :level="line.level"
                  :who="line.who"
                >{{ line.msg }}</ConsoleLineDS>
              </template>
              <div v-else class="feed__empty">
                <span v-if="isConnected">Waiting for output — try sending a command below</span>
                <span v-else>Console offline — server is not connected</span>
              </div>
            </div>

            <div class="console-bar">
              <span class="console-bar__prompt">&gt;</span>
              <Input
                v-model="consoleInput"
                :mono="true"
                size="sm"
                :placeholder="profile.mods === 'umod' ? 'say, kick, ban, oxide.reload …' : 'say, kick, ban …'"
                :disabled="!isConnected"
                style="flex: 1"
                @keydown.enter="sendConsoleCommand"
              />
              <Button
                size="sm"
                variant="secondary"
                icon="corner-down-left"
                :disabled="!isConnected"
                @click="sendConsoleCommand"
              >Send</Button>
            </div>
          </Panel>

        </div>

        <!-- Right sidebar -->
        <div class="dash__col dash__col--side">

          <!-- Resources — real stats from agent; null = '—' -->
          <Panel title="Resources" subtitle="re-Agent telemetry">
            <div class="solo-meters">
              <ResourceMeter
                label="CPU"
                :value="soloCpu ?? 0"
                :sub="soloCpu !== null ? soloCpu + '%' : 'awaiting telemetry'"
              />
              <ResourceMeter
                label="Memory"
                :value="soloRamPct ?? 0"
                :sub="soloRamSub ?? 'awaiting telemetry'"
              />
            </div>
            <div v-if="soloCpu === null && soloRamMb === null" class="meters-note">
              Resource metrics arrive via re-Agent heartbeat.
              <Button size="sm" variant="ghost" icon="server" class="meters-cta" @click="navServer">
                Agent setup
              </Button>
            </div>
          </Panel>

          <!-- Next wipe/reset — title follows game terminology -->
          <Panel :title="'Next ' + profile.terminology.reset.toLowerCase()">
            <div v-if="nextWipe" class="solo-wipe">
              <div>
                <div class="solo-wipe__type">{{ nextWipeType }}</div>
                <div class="solo-wipe__when">{{ nextWipeLabel }}</div>
                <div class="solo-wipe__name">{{ nextWipe.schedule_name }}</div>
              </div>
              <Button size="sm" variant="outline" icon="calendar-clock" @click="navWipes">Edit</Button>
            </div>
            <EmptyState
              v-else
              icon="calendar"
              :title="'No ' + profile.terminology.reset.toLowerCase() + ' scheduled'"
              :description="'Configure automatic ' + profile.terminology.reset.toLowerCase() + 's in the wipe manager.'"
            >
              <template #action>
                <Button size="sm" variant="outline" icon="calendar-clock" @click="navWipes">
                  Open wipe manager
                </Button>
              </template>
            </EmptyState>
          </Panel>

        </div>
      </div>
    </template>

    <!-- Loading state -->
    <template v-else>
      <div class="page__head">
        <div>
          <div class="t-eyebrow">Dashboard</div>
          <h1 class="page__title">Server cockpit</h1>
        </div>
      </div>
      <Panel>
        <div class="dash-loading">Loading server data…</div>
      </Panel>
    </template>

  </div>
</template>

<style scoped>
/* ---------- Shared shell ---------- */
.dash { max-width: 1480px; margin: 0 auto; display: flex; flex-direction: column; gap: 18px; }

.page__head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; row-gap: 12px;
}
.page__title {
  font-size: var(--text-3xl); font-weight: 700; letter-spacing: -0.02em;
  color: var(--text-primary); margin-top: 5px; white-space: nowrap;
}
.page__actions { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }

.dash__kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 13px; }
.dash__grid { display: grid; grid-template-columns: minmax(0, 1fr) 366px; gap: 16px; align-items: start; }
.dash__col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* ---------- Solo identity header ---------- */
.solo-id { display: flex; align-items: center; gap: 13px; }
.solo-id__chip {
  width: 42px; height: 42px; flex: none; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent); background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-border);
}
.solo-id__name {
  display: flex; align-items: center; gap: 10px;
  font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.01em;
  color: var(--text-primary); white-space: nowrap;
}
.solo-id__meta { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 3px; }

/* ---------- Chart loading ---------- */
.chart-loading {
  display: flex; align-items: center; justify-content: center;
  height: 196px; font-size: var(--text-sm); color: var(--text-muted);
}

/* ---------- Console feed ---------- */
.feed { padding: 8px 2px; max-height: 340px; overflow-y: auto; }
.feed--solo { max-height: 230px; }
.feed__empty {
  display: flex; align-items: center; justify-content: center;
  height: 100px; font-size: var(--text-sm); color: var(--text-muted);
  font-style: italic;
}

/* ---------- Console bar ---------- */
.console-bar {
  display: flex; align-items: center; gap: 9px; padding: 11px 12px;
  border-top: 1px solid var(--border-subtle); background: var(--surface-inset);
}
.console-bar__prompt { font-family: var(--font-mono); color: var(--accent-text); font-weight: 700; }

/* ---------- Resources ---------- */
.solo-meters { display: flex; flex-direction: column; gap: 13px; }
.meters-note {
  margin-top: 14px; font-size: var(--text-xs); color: var(--text-muted);
  border-top: 1px solid var(--border-subtle); padding-top: 12px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.meters-cta { margin-left: auto; }

/* ---------- Next wipe ---------- */
.solo-wipe { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.solo-wipe__type { font-size: var(--text-xs); color: var(--text-tertiary); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tracking-wider); margin-bottom: 3px; }
.solo-wipe__when { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.solo-wipe__name { font-size: var(--text-xs); color: var(--text-muted); margin-top: 2px; }

/* ---------- Loading ---------- */
.dash-loading {
  display: flex; align-items: center; justify-content: center;
  padding: 60px; font-size: var(--text-sm); color: var(--text-muted);
}

/* ---------- Responsive ---------- */
@media (max-width: 1180px) {
  .dash__grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
  .dash__kpis { grid-template-columns: repeat(2, 1fr); }
}
</style>
