<template>
  <div>
    <!-- At-a-glance strip: activity, upcoming checks, system health -->
    <v-row>
      <!-- Activity (last 7 days) with sparkline -->
      <v-col cols="12" md="4">
        <v-card class="h-100">
          <v-card-text>
            <div class="d-flex align-center mb-2">
              <v-icon size="small" class="mr-2" color="primary">mdi-chart-line</v-icon>
              <div class="text-caption text-medium-emphasis text-uppercase">Activity · 7 days</div>
              <v-spacer />
              <v-chip
                v-if="liveActivity"
                size="x-small"
                color="success"
                variant="tonal"
                class="live-chip"
              >
                <span class="live-dot mr-1" /> live
              </v-chip>
            </div>
            <ActivitySparkline
              :series="activitySeries"
              :height="54"
              class="mb-2"
            />
            <div class="d-flex align-center ga-4 text-caption">
              <div>
                <span class="text-h6 font-weight-bold text-success">
                  {{ formatNumber(activityTotals.files) }}
                </span>
                <span class="text-medium-emphasis ml-1">new files</span>
              </div>
              <div>
                <span class="text-h6 font-weight-bold">
                  {{ activityTotals.completed }}
                </span>
                <span class="text-medium-emphasis ml-1">runs</span>
              </div>
              <div v-if="activityTotals.failed">
                <span class="text-h6 font-weight-bold text-error">
                  {{ activityTotals.failed }}
                </span>
                <span class="text-medium-emphasis ml-1">failed</span>
              </div>
            </div>
          </v-card-text>
        </v-card>
      </v-col>

      <!-- Upcoming / In-flight -->
      <v-col cols="12" md="4">
        <v-card class="h-100">
          <v-card-text>
            <div class="d-flex align-center mb-2">
              <v-icon size="small" class="mr-2" color="info">mdi-timer-sand</v-icon>
              <div class="text-caption text-medium-emphasis text-uppercase">
                <span v-if="activeDownloadsCount">Running now</span>
                <span v-else>Next scheduled check</span>
              </div>
            </div>

            <div v-if="activeDownloadsCount" class="d-flex align-center">
              <div class="text-h5 font-weight-bold">
                {{ activeDownloadsCount }}
                <span class="text-body-2 text-medium-emphasis font-weight-regular">
                  in flight
                </span>
              </div>
              <v-spacer />
              <v-icon color="info" class="mdi-spin">mdi-loading</v-icon>
            </div>
            <div v-else-if="nextCheckCountdown" class="text-h5 font-weight-bold">
              {{ nextCheckCountdown }}
              <span class="text-body-2 text-medium-emphasis font-weight-regular ml-1">
                until next run
              </span>
            </div>
            <div v-else class="text-body-2 text-medium-emphasis">
              No sources scheduled
            </div>

            <v-divider class="my-2" />

            <div class="text-caption text-medium-emphasis mb-1">
              Up next
            </div>
            <div v-if="upcomingSources.length" class="upcoming-list">
              <div
                v-for="source in upcomingSources"
                :key="source.id"
                class="d-flex align-center text-caption py-1"
              >
                <v-icon
                  :color="getPlatformColor(source.platform)"
                  size="x-small"
                  class="mr-2"
                >
                  {{ getPlatformIcon(source.platform) }}
                </v-icon>
                <span class="upcoming-label">{{ getSourceLabel(source) }}</span>
                <v-spacer />
                <span class="text-medium-emphasis">
                  {{ source.nextCheckLabel }}
                </span>
              </div>
            </div>
            <div v-else class="text-caption text-medium-emphasis">
              All caught up
            </div>
          </v-card-text>
        </v-card>
      </v-col>

      <!-- System health: credentials + disk -->
      <v-col cols="12" md="4">
        <v-card class="h-100">
          <v-card-text>
            <div class="d-flex align-center mb-2">
              <v-icon size="small" class="mr-2" color="warning">mdi-shield-check</v-icon>
              <div class="text-caption text-medium-emphasis text-uppercase">System</div>
              <v-spacer />
              <v-chip
                v-if="credentialAlerts.length"
                size="x-small"
                :color="credentialAlerts.some(a => a.severity === 'error') ? 'error' : 'warning'"
                variant="tonal"
              >
                {{ credentialAlerts.length }} alert{{ credentialAlerts.length !== 1 ? 's' : '' }}
              </v-chip>
            </div>

            <div class="text-caption text-medium-emphasis mb-1">Credentials</div>
            <div class="d-flex flex-wrap ga-1 mb-3">
              <v-chip
                v-for="platform in supportedPlatforms"
                :key="platform"
                :color="credentialChipColor(platform)"
                :variant="credentialsStore.hasPlatformCredentials(platform) ? 'tonal' : 'outlined'"
                size="x-small"
                :to="credentialsStore.hasPlatformCredentials(platform) ? undefined : '/credentials'"
              >
                <v-icon start size="x-small">{{ getPlatformIcon(platform) }}</v-icon>
                {{ platform }}
                <v-icon
                  v-if="credentialWarningFor(platform)"
                  end
                  size="x-small"
                >
                  {{ credentialWarningFor(platform).severity === 'error' ? 'mdi-alert-circle' : 'mdi-alert' }}
                </v-icon>
              </v-chip>
            </div>

            <div
              v-for="alert in credentialAlerts"
              :key="alert.platform"
              class="text-caption mb-1"
              :class="alert.severity === 'error' ? 'text-error' : 'text-warning'"
            >
              <v-icon size="x-small" class="mr-1">
                {{ alert.severity === 'error' ? 'mdi-alert-circle' : 'mdi-alert' }}
              </v-icon>
              {{ alert.message }}
            </div>

            <v-divider class="my-2" />

            <div class="d-flex align-center mb-1">
              <div class="text-caption text-medium-emphasis">Disk</div>
              <v-spacer />
              <div class="text-caption text-medium-emphasis">
                <template v-if="storageStats?.disk_free_formatted">
                  {{ storageStats.disk_free_formatted }} free
                </template>
                <template v-else>—</template>
              </div>
            </div>
            <v-progress-linear
              :model-value="storageStats?.disk_percent_used || 0"
              :color="diskBarColor"
              bg-color="grey-lighten-2"
              height="8"
              rounded
            />
            <div class="text-caption text-medium-emphasis mt-1">
              <template v-if="storageStats?.disk_used_formatted">
                {{ storageStats.disk_used_formatted }} of {{ storageStats.disk_total_formatted }}
                used ({{ storageStats.disk_percent_used }}%)
              </template>
              <template v-else-if="storageStats?.total_size_formatted">
                Downloads: {{ storageStats.total_size_formatted }}
                <span v-if="storageStats?.file_count">
                  ({{ storageStats.file_count.toLocaleString() }} files)
                </span>
              </template>
              <template v-else>Calculating…</template>
            </div>
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>

    <!-- Action bar -->
    <v-row class="mt-4">
      <v-col cols="12">
        <v-card>
          <v-card-text class="d-flex align-center ga-3 flex-wrap">
            <v-btn
              color="primary"
              prepend-icon="mdi-refresh"
              :loading="checkingAll"
              @click="checkAllSources"
            >
              <template #loader>
                <v-progress-circular indeterminate size="18" width="2" class="mr-2" />
                Checking…
              </template>
              Check All Sources
            </v-btn>
            <v-btn
              color="warning"
              prepend-icon="mdi-replay"
              :loading="retryingAll"
              :disabled="!failedCount"
              @click="retryAllFailed"
            >
              <template #loader>
                <v-progress-circular indeterminate size="18" width="2" class="mr-2" />
                Retrying…
              </template>
              Retry All Failed
            </v-btn>
            <v-btn
              color="secondary"
              variant="outlined"
              prepend-icon="mdi-broom"
              :loading="resettingOrphaned"
              :disabled="!stuckRunningCount"
              @click="resetOrphanedJobs"
            >
              <template #loader>
                <v-progress-circular indeterminate size="18" width="2" class="mr-2" />
                Resetting…
              </template>
              Reset Stuck ({{ stuckRunningCount }})
            </v-btn>
            <v-spacer />
            <v-btn
              icon
              size="small"
              variant="text"
              :loading="manualRefreshing"
              @click="manualRefresh"
              title="Refresh now"
            >
              <v-icon>mdi-refresh</v-icon>
            </v-btn>
            <div class="text-caption text-medium-emphasis">
              Updated {{ lastRefreshLabel }}
            </div>
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>

    <!-- Active + Recent Activity -->
    <v-row class="mt-4">
      <v-col cols="12" md="4">
        <v-card class="h-100 d-flex flex-column">
          <v-card-title class="d-flex align-center">
            <v-icon class="mr-2" :class="{ 'mdi-spin': activeDownloads.length }">
              {{ activeDownloads.length ? 'mdi-loading' : 'mdi-download' }}
            </v-icon>
            Active
            <v-chip v-if="activeDownloads.length" size="small" color="info" class="ml-2">
              {{ activeDownloads.length }}
            </v-chip>
            <v-spacer />
            <v-btn
              v-if="activeDownloads.length"
              icon
              size="x-small"
              variant="text"
              @click="refreshActive"
            >
              <v-icon>mdi-refresh</v-icon>
            </v-btn>
          </v-card-title>
          <v-card-text class="flex-grow-1">
            <v-list v-if="activeDownloads.length" density="compact">
              <v-list-item
                v-for="dl in visibleActiveDownloads"
                :key="dl.id"
                :class="{ 'download-running': dl.status === 'running' }"
                @click="showDetails(dl)"
              >
                <template v-slot:prepend>
                  <v-icon :color="dl.status === 'running' ? 'info' : 'warning'" size="small">
                    {{ dl.status === 'running' ? 'mdi-loading mdi-spin' : 'mdi-clock-outline' }}
                  </v-icon>
                </template>
                <v-list-item-title class="text-body-2">
                  {{ getDownloadLabel(dl) }}
                </v-list-item-title>
                <v-list-item-subtitle>
                  <v-chip size="x-small" :color="dl.status === 'running' ? 'info' : 'warning'" variant="tonal">
                    {{ dl.status }}
                  </v-chip>
                </v-list-item-subtitle>
              </v-list-item>
            </v-list>
            <div v-else class="text-center py-8">
              <v-icon size="48" color="secondary" :style="{ opacity: 0.5 }">mdi-check-circle-outline</v-icon>
              <div class="text-body-1 mt-2">No active downloads</div>
              <div class="text-caption text-medium-emphasis mt-1">All sources are idle</div>
            </div>
          </v-card-text>
          <v-card-actions>
            <v-btn variant="text" :to="hiddenActiveCount ? '/downloads?status=queued' : '/downloads'">
              {{ hiddenActiveCount ? `View all downloads (+${hiddenActiveCount} more queued)` : 'View all downloads' }}
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>

      <v-col cols="12" md="8">
        <v-card class="h-100 d-flex flex-column">
          <v-card-title>
            Recent Activity
            <v-chip v-if="recentActivity.length" size="small" class="ml-2" variant="tonal">
              Last 7 days
            </v-chip>
          </v-card-title>
          <v-card-text class="flex-grow-1">
            <v-list v-if="recentActivityGrouped.length">
              <template v-for="entry in visibleRecentActivity" :key="entry.key">
                <v-list-item
                  v-if="entry.kind === 'single'"
                  link
                  @click="showDetails(entry.download)"
                >
                  <template v-slot:prepend>
                    <v-icon :color="getStatusColor(entry.download.status)">
                      {{ getStatusIcon(entry.download.status) }}
                    </v-icon>
                  </template>
                  <v-list-item-title>
                    {{ getDownloadLabel(entry.download) }}
                  </v-list-item-title>
                  <v-list-item-subtitle>
                    {{ formatRelativeTime(entry.download.created_at) }}
                    <span v-if="entry.download.file_count > 0" class="text-success ml-2">
                      {{ entry.download.file_count }} new file{{ entry.download.file_count !== 1 ? 's' : '' }}
                    </span>
                  </v-list-item-subtitle>
                  <template v-slot:append>
                    <v-chip
                      :color="getStatusColor(entry.download.status)"
                      size="small"
                      variant="tonal"
                    >
                      {{ entry.download.status === 'completed' ? 'new content' : entry.download.status }}
                    </v-chip>
                  </template>
                </v-list-item>
                <v-list-item
                  v-else
                  link
                  @click="showDetails(entry.latest)"
                >
                  <template v-slot:prepend>
                    <v-icon color="error">mdi-alert-circle</v-icon>
                  </template>
                  <v-list-item-title>{{ entry.label }}</v-list-item-title>
                  <v-list-item-subtitle>
                    {{ entry.items.length }} failure{{ entry.items.length !== 1 ? 's' : '' }} · latest {{ formatRelativeTime(entry.latest.created_at) }}
                  </v-list-item-subtitle>
                  <template v-slot:append>
                    <v-chip color="error" size="small" variant="tonal">
                      {{ entry.items.length }}× failed
                    </v-chip>
                  </template>
                </v-list-item>
              </template>
            </v-list>
            <div v-else class="text-center py-8">
              <v-icon size="48" color="secondary" :style="{ opacity: 0.5 }">mdi-history</v-icon>
              <div class="text-body-1 mt-2">No recent activity</div>
              <div class="text-caption text-medium-emphasis mt-1">Failures and new downloads appear here</div>
            </div>
          </v-card-text>
          <v-card-actions>
            <v-btn variant="text" to="/downloads">
              {{ hiddenRecentCount ? `View all downloads (+${hiddenRecentCount} more)` : 'View all downloads' }}
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>

    <!-- Sources needing attention -->
    <v-row class="mt-4">
      <v-col cols="12">
        <v-card>
          <v-card-title>
            Sources Needing Attention
            <v-chip class="ml-2" color="error" size="small" v-if="totalFailingCount">
              {{ totalFailingCount }}
            </v-chip>
          </v-card-title>
          <v-card-text>
            <div class="mb-4">
              <div class="d-flex align-center mb-2">
                <div class="text-caption text-medium-emphasis">Platform Health</div>
                <v-spacer />
                <div class="d-flex align-center text-caption text-medium-emphasis">
                  <span class="legend-swatch legend-swatch--ok mr-1" />
                  <span class="mr-3">healthy</span>
                  <span class="legend-swatch legend-swatch--bad mr-1" />
                  <span>failing</span>
                </div>
              </div>
              <div
                v-for="row in platformHealth"
                :key="row.platform"
                class="d-flex align-center mb-2"
              >
                <v-icon :color="getPlatformColor(row.platform)" size="small" class="mr-2">
                  {{ getPlatformIcon(row.platform) }}
                </v-icon>
                <div class="text-body-2 platform-label">{{ row.platform }}</div>
                <v-progress-linear
                  :model-value="row.total > 0 ? ((row.total - row.failing) / row.total) * 100 : 0"
                  color="success"
                  bg-color="error"
                  bg-opacity="1"
                  height="12"
                  rounded
                  class="mx-3 flex-grow-1"
                >
                  <v-tooltip activator="parent" location="top">
                    {{ row.total - row.failing }} of {{ row.total }} {{ row.platform }} source{{ row.total !== 1 ? 's' : '' }} healthy<template v-if="row.failing"> ({{ row.failing }} failing)</template>
                  </v-tooltip>
                </v-progress-linear>
                <div class="text-caption text-medium-emphasis count-label">
                  {{ row.total - row.failing }}/{{ row.total }}
                </div>
              </div>
              <div
                v-if="!platformHealth.length"
                class="text-caption text-medium-emphasis text-center py-2"
              >
                No enabled sources
              </div>
            </div>

            <v-divider class="my-3" />

            <v-table v-if="failingSources.length" density="compact">
              <thead>
                <tr>
                  <th>Source</th>
                  <th>Consecutive failures</th>
                  <th>Last check</th>
                  <th>Actions</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="source in failingSources" :key="source.id">
                  <td>{{ getSourceLabel(source) }}</td>
                  <td>
                    <v-chip color="error" size="small">{{ source.error_count }}</v-chip>
                  </td>
                  <td><span :title="formatDate(source.last_check)">{{ source.last_check ? formatRelativeTime(source.last_check) : 'Never' }}</span></td>
                  <td>
                    <v-btn
                      size="small"
                      variant="text"
                      color="primary"
                      @click="retrySource(source)"
                    >
                      Retry
                    </v-btn>
                    <v-btn size="small" variant="text" to="/subscriptions">
                      View
                      <v-tooltip activator="parent">View in Subscriptions</v-tooltip>
                    </v-btn>
                  </td>
                </tr>
              </tbody>
            </v-table>
            <div v-else class="text-caption text-medium-emphasis text-center py-3">
              No sources in failing state
            </div>
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>

    <DownloadDetailsModal
      v-model="detailsDialog"
      :download="selectedDownload"
      @retry="handleRetryFromModal"
    />
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useSourcesStore } from '../stores/sources'
import { useDownloadsStore } from '../stores/downloads'
import { useCredentialsStore } from '../stores/credentials'
import { useNotificationStore } from '../stores/notifications'
import { useSettingsStore } from '../stores/settings'
import { settingsApi, downloadsApi, credentialsApi } from '../services/api'
import ActivitySparkline from '../components/ActivitySparkline.vue'
import DownloadDetailsModal from '../components/DownloadDetailsModal.vue'

const sourcesStore = useSourcesStore()
const downloadsStore = useDownloadsStore()
const credentialsStore = useCredentialsStore()
const notifications = useNotificationStore()
const settingsStore = useSettingsStore()

const supportedPlatforms = ['patreon', 'subscribestar', 'hentaifoundry', 'discord', 'pixiv', 'deviantart']

// State
const checkingAll = ref(false)
const retryingAll = ref(false)
const resettingOrphaned = ref(false)
const manualRefreshing = ref(false)
const storageStats = ref(null)
const activeDownloads = ref([])
const detailsDialog = ref(false)
const selectedDownload = ref(null)
const credentialItems = ref([])
// Tick every 30s so "X min until next run" and "Updated 2 min ago" stay fresh
// without needing a fetch.
const now = ref(new Date())
const lastRefreshedAt = ref(null)
let refreshInterval = null
let tickInterval = null
let visibilityHandler = null

// Computed
const stats = computed(() => downloadsStore.stats)
const recentActivity = computed(() => downloadsStore.recentActivity)
const activityTimeline = computed(() => downloadsStore.activityTimeline)

const activitySeries = computed(() => activityTimeline.value?.series || [])
const activityTotals = computed(() =>
  activityTimeline.value?.totals || { completed: 0, failed: 0, files: 0 }
)
const liveActivity = computed(() => activeDownloadsCount.value > 0)

const recentActivityGrouped = computed(() => {
  const singles = []
  const groups = new Map()
  for (const d of recentActivity.value) {
    if (d.status === 'failed') {
      const key = `${d.subscription_name || '?'}:${d.platform || '?'}`
      const g = groups.get(key) || { kind: 'group', key, label: key, items: [], latest: d }
      g.items.push(d)
      if (new Date(d.created_at) > new Date(g.latest.created_at)) g.latest = d
      groups.set(key, g)
    } else {
      singles.push({ kind: 'single', key: `d-${d.id}`, download: d })
    }
  }
  const out = [...singles, ...groups.values()]
  out.sort((a, b) => {
    const tA = new Date(a.kind === 'single' ? a.download.created_at : a.latest.created_at)
    const tB = new Date(b.kind === 'single' ? b.download.created_at : b.latest.created_at)
    return tB - tA
  })
  return out
})

const DASHBOARD_LIST_LIMIT = 5
const visibleActiveDownloads = computed(() => activeDownloads.value.slice(0, DASHBOARD_LIST_LIMIT))
const hiddenActiveCount = computed(() =>
  Math.max(0, activeDownloads.value.length - DASHBOARD_LIST_LIMIT)
)
const visibleRecentActivity = computed(() => recentActivityGrouped.value.slice(0, DASHBOARD_LIST_LIMIT))
const hiddenRecentCount = computed(() =>
  Math.max(0, recentActivityGrouped.value.length - DASHBOARD_LIST_LIMIT)
)

const failureThreshold = computed(() => {
  const n = Number(settingsStore.settings['dashboard.failure_threshold'])
  return Number.isFinite(n) && n >= 1 ? n : 5
})

const scheduleIntervalSeconds = computed(() => {
  const n = Number(settingsStore.settings['download.schedule_interval'])
  return Number.isFinite(n) && n > 0 ? n : 28800
})

// Annotate enabled sources with next_check derived from last_check + interval.
// Never-checked sources are treated as immediately due so they sort to the top.
const sourcesWithSchedule = computed(() => {
  const nowTime = now.value.getTime()
  const intervalMs = scheduleIntervalSeconds.value * 1000
  return sourcesStore.sources
    .filter((s) => s.enabled)
    .map((s) => {
      const lastCheckMs = s.last_check ? new Date(s.last_check).getTime() : null
      const nextMs = lastCheckMs !== null ? lastCheckMs + intervalMs : nowTime
      return { ...s, nextCheckMs: nextMs, nextCheckLabel: formatCountdown(nextMs - nowTime) }
    })
    .sort((a, b) => a.nextCheckMs - b.nextCheckMs)
})

const upcomingSources = computed(() => sourcesWithSchedule.value.slice(0, 4))

const nextCheckCountdown = computed(() => {
  const next = sourcesWithSchedule.value[0]
  if (!next) return null
  const delta = next.nextCheckMs - now.value.getTime()
  return formatCountdown(delta)
})

const platformHealth = computed(() => {
  const enabled = sourcesStore.sources.filter((s) => s.enabled)
  return supportedPlatforms
    .map((platform) => {
      const sources = enabled.filter((s) => s.platform === platform)
      if (sources.length === 0) return null
      const failing = sources.filter(
        (s) => (s.error_count || 0) >= failureThreshold.value
      ).length
      return { platform, total: sources.length, failing }
    })
    .filter(Boolean)
})

const failingSources = computed(() =>
  sourcesStore.sources
    .filter((s) => s.enabled && (s.error_count || 0) >= failureThreshold.value)
    .sort((a, b) => new Date(b.last_check || 0) - new Date(a.last_check || 0))
)

const totalFailingCount = computed(() => failingSources.value.length)
const failedCount = computed(() => stats.value?.active_failed || 0)
const activeDownloadsCount = computed(
  () => (stats.value?.queued || 0) + (stats.value?.running || 0)
)
const stuckRunningCount = computed(() => stats.value?.running || 0)

// Credential alerts: "expiring soon" (warning) within 7 days, "expired" (error)
// past now, or "missing" for platforms with enabled sources and no credential.
const CREDENTIAL_WARN_DAYS = 7
const credentialAlerts = computed(() => {
  const alerts = []
  const nowMs = now.value.getTime()
  const warnMs = nowMs + CREDENTIAL_WARN_DAYS * 86400000

  // Expiration alerts from stored credentials
  for (const cred of credentialItems.value) {
    if (!cred.expires_at) continue
    const expMs = new Date(cred.expires_at).getTime()
    if (expMs <= nowMs) {
      alerts.push({
        platform: cred.platform,
        severity: 'error',
        message: `${cred.platform} credentials expired ${formatRelativeTime(cred.expires_at)}`,
      })
    } else if (expMs <= warnMs) {
      alerts.push({
        platform: cred.platform,
        severity: 'warning',
        message: `${cred.platform} credentials expire ${formatCountdown(expMs - nowMs)}`,
      })
    }
  }

  // "Missing" alerts only for platforms that actually have enabled sources —
  // otherwise we'd nag about platforms the user doesn't use.
  const usedPlatforms = new Set(
    sourcesStore.sources.filter((s) => s.enabled).map((s) => s.platform)
  )
  for (const platform of usedPlatforms) {
    if (!credentialsStore.hasPlatformCredentials(platform)) {
      alerts.push({
        platform,
        severity: 'warning',
        message: `${platform} has enabled sources but no credentials stored`,
      })
    }
  }
  return alerts
})

function credentialWarningFor(platform) {
  return credentialAlerts.value.find((a) => a.platform === platform)
}

function credentialChipColor(platform) {
  const alert = credentialWarningFor(platform)
  if (alert?.severity === 'error') return 'error'
  if (alert?.severity === 'warning') return 'warning'
  if (credentialsStore.hasPlatformCredentials(platform)) return 'success'
  return 'grey'
}

const diskBarColor = computed(() => {
  const pct = storageStats.value?.disk_percent_used
  if (pct == null) return 'primary'
  if (pct >= 90) return 'error'
  if (pct >= 75) return 'warning'
  return 'success'
})

const lastRefreshLabel = computed(() => {
  if (!lastRefreshedAt.value) return 'just now'
  // Force reactivity on `now` so the label recomputes as time passes.
  const _ = now.value
  return formatRelativeTime(lastRefreshedAt.value)
})

// Lifecycle
onMounted(() => {
  loadDashboardData()
  startAutoRefresh()
  startTick()
  attachVisibilityHandler()
})

onUnmounted(() => {
  stopAutoRefresh()
  stopTick()
  detachVisibilityHandler()
})

function loadDashboardData() {
  sourcesStore.fetchSources().catch((e) => console.error('Failed to fetch sources:', e))
  credentialsStore.fetchCredentials().catch((e) => console.error('Failed to fetch credentials:', e))
  settingsStore.fetchSettings().catch((e) => console.error('Failed to fetch settings:', e))

  downloadsStore.fetchStats().catch((e) => console.error('Failed to fetch stats:', e))
  downloadsStore
    .fetchRecentActivity({ limit: 10 })
    .catch((e) => console.error('Failed to fetch recent activity:', e))
  downloadsStore
    .fetchActivityTimeline({ days: 7 })
    .catch((e) => console.error('Failed to fetch activity timeline:', e))

  fetchActiveDownloads()
  fetchStorageStats()
  fetchCredentialItems()
  lastRefreshedAt.value = new Date()
}

async function fetchActiveDownloads() {
  try {
    const [queuedResponse, runningResponse] = await Promise.all([
      downloadsStore.fetchDownloads({ status: 'queued', per_page: 20 }),
      downloadsStore.fetchDownloads({ status: 'running', per_page: 20 }),
    ])
    const running = runningResponse.items.filter((d) => d.status === 'running')
    const queued = queuedResponse.items.filter((d) => d.status === 'queued')
    activeDownloads.value = [...running, ...queued]
  } catch (e) {
    console.error('Failed to fetch active downloads:', e)
  }
}

async function fetchStorageStats() {
  try {
    const response = await settingsApi.get()
    storageStats.value = response.data.storage_stats
  } catch (e) {
    console.error('Failed to fetch storage stats:', e)
  }
}

async function fetchCredentialItems() {
  try {
    const response = await credentialsApi.list()
    credentialItems.value = response.data.items || []
  } catch (e) {
    console.error('Failed to fetch credentials:', e)
  }
}

// Two-speed polling: 5s when something's running, 30s otherwise. Tucked behind
// the Page Visibility API so we don't hammer the API when the tab is hidden.
function startAutoRefresh() {
  scheduleNextRefresh()
}

function scheduleNextRefresh() {
  stopAutoRefresh()
  const delay = activeDownloadsCount.value > 0 ? 5000 : 30000
  refreshInterval = setTimeout(async () => {
    if (!document.hidden) {
      await refreshLive()
    }
    scheduleNextRefresh()
  }, delay)
}

function stopAutoRefresh() {
  if (refreshInterval) {
    clearTimeout(refreshInterval)
    refreshInterval = null
  }
}

async function refreshLive() {
  try {
    await Promise.all([
      downloadsStore.fetchStats(),
      downloadsStore.fetchRecentActivity({ limit: 10 }),
      downloadsStore.fetchActivityTimeline({ days: 7 }),
      fetchActiveDownloads(),
      sourcesStore.fetchSources(),
    ])
    lastRefreshedAt.value = new Date()
  } catch (e) {
    console.error('Auto-refresh failed:', e)
  }
}

function startTick() {
  tickInterval = setInterval(() => {
    now.value = new Date()
  }, 30000)
}

function stopTick() {
  if (tickInterval) {
    clearInterval(tickInterval)
    tickInterval = null
  }
}

function attachVisibilityHandler() {
  visibilityHandler = () => {
    if (!document.hidden) {
      // Tab just regained focus — pull a fresh snapshot immediately so the
      // user never sees stale data after a long break.
      refreshLive()
    }
  }
  document.addEventListener('visibilitychange', visibilityHandler)
}

function detachVisibilityHandler() {
  if (visibilityHandler) {
    document.removeEventListener('visibilitychange', visibilityHandler)
    visibilityHandler = null
  }
}

async function manualRefresh() {
  manualRefreshing.value = true
  try {
    await refreshLive()
    await fetchStorageStats()
    await fetchCredentialItems()
  } finally {
    manualRefreshing.value = false
  }
}

async function refreshActive() {
  await fetchActiveDownloads()
}

async function checkAllSources() {
  checkingAll.value = true
  try {
    const enabledSources = sourcesStore.sources.filter((s) => s.enabled)
    const results = await Promise.allSettled(
      enabledSources.map((source) => sourcesStore.triggerCheck(source.id))
    )
    const queued = results.filter((r) => r.status === 'fulfilled').length
    const failed = results.filter((r) => r.status === 'rejected').length
    if (failed > 0) {
      notifications.success(`Queued ${queued} sources (${failed} failed to queue)`)
    } else {
      notifications.success(`Queued checks for ${queued} sources`)
    }
    await refreshLive()
  } catch (error) {
    notifications.error(`Failed to check sources: ${error.message}`)
  } finally {
    checkingAll.value = false
  }
}

async function retryAllFailed() {
  retryingAll.value = true
  try {
    const response = await downloadsStore.retryFailedBulk()
    const {
      retried,
      skipped_duplicate_source: dupSkipped = 0,
      skipped_recent_success: recentSkipped = 0,
      skipped_no_source: noSourceSkipped = 0,
    } = response.data

    const parts = [`Queued ${retried} ${retried === 1 ? 'retry' : 'retries'}`]
    if (dupSkipped) parts.push(`${dupSkipped} duplicate-source skipped`)
    if (recentSkipped) parts.push(`${recentSkipped} recently succeeded, skipped`)
    if (noSourceSkipped) parts.push(`${noSourceSkipped} orphaned, skipped`)
    notifications.success(parts.join(' · '))

    await refreshLive()
  } catch (error) {
    notifications.error(`Failed to retry downloads: ${error.message}`)
  } finally {
    retryingAll.value = false
  }
}

async function resetOrphanedJobs() {
  resettingOrphaned.value = true
  try {
    const response = await downloadsApi.resetOrphaned(0)
    notifications.success(response.data.message || `Reset ${response.data.reset_count} stuck jobs`)
    await refreshLive()
  } catch (error) {
    notifications.error(`Failed to reset stuck jobs: ${error.message}`)
  } finally {
    resettingOrphaned.value = false
  }
}

function showDetails(download) {
  selectedDownload.value = download
  detailsDialog.value = true
}

async function handleRetryFromModal(download) {
  detailsDialog.value = false
  try {
    await downloadsStore.retryDownload(download.id)
    notifications.success('Download queued for retry')
    await refreshLive()
  } catch (error) {
    notifications.error(`Failed to retry: ${error.message}`)
  }
}

// Formatting helpers
function getStatusIcon(status) {
  const icons = {
    completed: 'mdi-check-circle',
    failed: 'mdi-alert-circle',
    running: 'mdi-loading mdi-spin',
    queued: 'mdi-clock-outline',
    pending: 'mdi-clock-outline',
    skipped: 'mdi-skip-next',
  }
  return icons[status] || 'mdi-help-circle'
}

function getPlatformIcon(platform) {
  const icons = {
    patreon: 'mdi-patreon',
    subscribestar: 'mdi-star',
    hentaifoundry: 'mdi-palette',
    discord: 'mdi-discord',
    pixiv: 'mdi-alpha-p-box',
    deviantart: 'mdi-deviantart',
  }
  return icons[platform] || 'mdi-web'
}

function getPlatformColor(platform) {
  const colors = {
    patreon: 'red',
    subscribestar: 'amber',
    hentaifoundry: 'purple',
    discord: 'indigo',
    pixiv: 'blue',
    deviantart: 'green',
  }
  return colors[platform] || 'grey'
}

function getStatusColor(status) {
  const colors = {
    completed: 'success',
    failed: 'error',
    running: 'info',
    queued: 'warning',
    pending: 'warning',
    skipped: 'grey',
  }
  return colors[status] || 'grey'
}

function formatDate(dateStr) {
  if (!dateStr) return 'Never'
  return new Date(dateStr).toLocaleString()
}

function formatRelativeTime(dateStr) {
  if (!dateStr) return 'Unknown'
  const date = new Date(dateStr)
  const diffMs = now.value - date
  const past = diffMs >= 0
  const absMs = Math.abs(diffMs)
  const diffMins = Math.floor(absMs / 60000)
  const diffHours = Math.floor(absMs / 3600000)
  const diffDays = Math.floor(absMs / 86400000)

  if (diffMins < 1) return past ? 'just now' : 'soon'
  const suffix = past ? 'ago' : 'from now'
  if (diffMins < 60) return `${diffMins} min${diffMins !== 1 ? 's' : ''} ${suffix}`
  if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ${suffix}`
  if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ${suffix}`
  return date.toLocaleDateString()
}

// Humanize a duration in ms as "in 12 min" / "in 3 hours" / "now" (or similar
// past form). Past deltas read as "overdue".
function formatCountdown(deltaMs) {
  if (deltaMs <= 0) {
    const overdueMins = Math.floor(-deltaMs / 60000)
    if (overdueMins < 1) return 'now'
    if (overdueMins < 60) return `overdue ${overdueMins}m`
    const h = Math.floor(overdueMins / 60)
    return `overdue ${h}h`
  }
  const mins = Math.floor(deltaMs / 60000)
  if (mins < 1) return 'now'
  if (mins < 60) return `in ${mins}m`
  const hours = Math.floor(mins / 60)
  if (hours < 24) return `in ${hours}h ${mins % 60}m`
  const days = Math.floor(hours / 24)
  return `in ${days}d ${hours % 24}h`
}

function formatNumber(n) {
  if (n == null) return '0'
  if (n < 1000) return String(n)
  if (n < 10000) return `${(n / 1000).toFixed(1)}k`
  return `${Math.round(n / 1000)}k`
}

function truncate(str, length) {
  if (!str) return ''
  if (str.length <= length) return str
  return str.substring(0, length) + '...'
}

function getDownloadLabel(download) {
  if (download.subscription_name && download.platform) {
    return `${download.subscription_name}:${download.platform}`
  }
  return truncate(download.url, 35)
}

function getSourceLabel(source) {
  const name = source.subscription_name || source.subscription?.name || 'Unknown'
  return `${name}:${source.platform}`
}

async function retrySource(source) {
  try {
    await sourcesStore.triggerCheck(source.id)
    notifications.success(`Queued check for ${getSourceLabel(source)}`)
    await refreshLive()
  } catch (error) {
    notifications.error(`Failed to queue check: ${error.message}`)
  }
}
</script>

<style scoped>
.download-running {
  border-left: 3px solid rgb(var(--v-theme-primary));
  animation: pulse-border 2s ease-in-out infinite;
}

@keyframes pulse-border {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

.platform-label {
  min-width: 110px;
  text-transform: capitalize;
}

.count-label {
  min-width: 50px;
  text-align: right;
}

.legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
.legend-swatch--ok  { background-color: rgb(var(--v-theme-success)); }
.legend-swatch--bad { background-color: rgb(var(--v-theme-error)); }

.upcoming-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 60%;
}

.live-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: rgb(var(--v-theme-success));
  animation: pulse-dot 1.4s ease-in-out infinite;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(1.3); }
}
</style>
