<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from "vue";

import { fetchDashboardSnapshot, type DashboardSnapshot } from "@/features/dashboard";
import { formatCooldownCountdown } from "@/features/safety";
import { ApiRequestError } from "@/lib/api/errors";
import AppPage from "@/components/layout/AppPage.vue";
import AsyncStateGate from "@/components/patterns/AsyncStateGate.vue";
import QueueHealthBadge from "@/components/patterns/QueueHealthBadge.vue";
import RequestStateAlerts from "@/components/patterns/RequestStateAlerts.vue";
import RunStatusBadge from "@/components/patterns/RunStatusBadge.vue";
import ScrapeSafetyBanner from "@/components/patterns/ScrapeSafetyBanner.vue";
import AppButton from "@/components/ui/AppButton.vue";
import AppCard from "@/components/ui/AppCard.vue";
import AppEmptyState from "@/components/ui/AppEmptyState.vue";
import AppHelpHint from "@/components/ui/AppHelpHint.vue";
import { useAuthStore } from "@/stores/auth";
import { useRunStatusStore } from "@/stores/run_status";
import { useUserSettingsStore } from "@/stores/user_settings";

const loading = ref(true);
const errorMessage = ref<string | null>(null);
const errorRequestId = ref<string | null>(null);
const successMessage = ref<string | null>(null);
const snapshot = ref<DashboardSnapshot | null>(null);
const refreshingAfterCompletion = ref(false);
const auth = useAuthStore();
const runStatus = useRunStatusStore();
const userSettings = useUserSettingsStore();
const DASHBOARD_RUN_STATUS_SYNC_INTERVAL_MS = 5000;
let runStatusSyncTimer: ReturnType<typeof setInterval> | null = null;

const isStartBlocked = computed(
  () =>
    runStatus.isRunActive ||
    !userSettings.manualRunAllowed ||
    runStatus.safetyState.cooldown_active,
);
const startCheckDisabledReason = computed(() => {
  if (!userSettings.manualRunAllowed) {
    return "Manual checks are disabled by server policy.";
  }
  if (runStatus.safetyState.cooldown_active) {
    return runStatus.safetyState.cooldown_reason_label || "Safety cooldown is active.";
  }
  if (runStatus.isRunActive) {
    return "A check is already in progress.";
  }
  return null;
});

const startCheckLabel = computed(() => {
  if (!userSettings.manualRunAllowed) {
    return "Manual checks disabled";
  }
  if (runStatus.safetyState.cooldown_active) {
    const remaining = runStatus.safetyState.cooldown_remaining_seconds;
    if (remaining > 0) {
      return `Cooldown (${formatCooldownCountdown(remaining)})`;
    }
    return "Safety cooldown";
  }
  if (runStatus.isLikelyRunning) {
    return "Check in progress";
  }
  if (runStatus.isSubmitting) {
    return "Starting...";
  }
  return "Start check";
});
const isStartCheckAnimating = computed(
  () => runStatus.isSubmitting || runStatus.isLikelyRunning,
);
const isCancelAnimating = ref(false);

const displayedLatestRun = computed(() => {
  const snapshotLatest = snapshot.value?.latestRun ?? null;
  const sharedLatest = runStatus.latestRun;

  if (!snapshotLatest) {
    return sharedLatest;
  }
  if (!sharedLatest) {
    return snapshotLatest;
  }
  if (sharedLatest.id > snapshotLatest.id) {
    return sharedLatest;
  }
  if (sharedLatest.id < snapshotLatest.id) {
    return snapshotLatest;
  }
  return sharedLatest;
});

const recentRuns = computed(() => {
  const baseRuns = snapshot.value?.recentRuns ?? [];
  const mergedRuns = [...baseRuns];
  const latest = displayedLatestRun.value;

  if (!latest) {
    return mergedRuns;
  }

  const existingIndex = mergedRuns.findIndex((run) => run.id === latest.id);
  if (existingIndex === -1) {
    mergedRuns.unshift(latest);
    return mergedRuns;
  }

  mergedRuns.splice(existingIndex, 1, latest);
  return mergedRuns;
});

const recentPublications = computed(() => {
  const stream = runStatus.livePublications;
  // DashboardSnapshot's recentPublications is compatible with PublicationItem,
  // but let's cast it slightly to guarantee iteration.
  const base = snapshot.value?.recentPublications ?? [];
  const merged = [...stream, ...base];
  
  const seenIds = new Set();
  const deduped: any[] = [];
  for (const item of merged) {
    if (!seenIds.has(item.publication_id)) {
      seenIds.add(item.publication_id);
      deduped.push(item);
    }
  }
  return deduped;
});

const activeRunId = computed(() => runStatus.latestRun?.status === "running" ? runStatus.latestRun.id : null);
const showRunningHint = computed(
  () => runStatus.isLikelyRunning && displayedLatestRun.value?.status !== "running",
);

function formatDate(value: string | null): string {
  if (!value) {
    return "n/a";
  }
  const asDate = new Date(value);
  if (Number.isNaN(asDate.getTime())) {
    return value;
  }
  return asDate.toLocaleString();
}

function shouldRefreshAfterRunChange(
  nextRun: typeof runStatus.latestRun,
  previousRun: typeof runStatus.latestRun,
): boolean {
  if (!nextRun || !previousRun) {
    return false;
  }
  if (nextRun.id !== previousRun.id) {
    return true;
  }
  if (nextRun.status === previousRun.status) {
    return false;
  }
  const nextActive = nextRun.status === "running" || nextRun.status === "resolving";
  const previousActive = previousRun.status === "running" || previousRun.status === "resolving";
  return nextActive || previousActive;
}

function startRunStatusSyncLoop(): void {
  if (runStatusSyncTimer !== null) {
    return;
  }
  runStatusSyncTimer = setInterval(() => {
    if (runStatus.isRunActive) {
      return;
    }
    void runStatus.syncLatest();
  }, DASHBOARD_RUN_STATUS_SYNC_INTERVAL_MS);
}

function stopRunStatusSyncLoop(): void {
  if (runStatusSyncTimer === null) {
    return;
  }
  clearInterval(runStatusSyncTimer);
  runStatusSyncTimer = null;
}

async function loadSnapshot(): Promise<void> {
  loading.value = true;
  errorMessage.value = null;
  errorRequestId.value = null;

  try {
    const dashboardSnapshot = await fetchDashboardSnapshot();
    snapshot.value = dashboardSnapshot;
    runStatus.setLatestRun(dashboardSnapshot.latestRun);
    runStatus.setSafetyState(dashboardSnapshot.safetyState);
  } catch (error) {
    snapshot.value = null;
    if (error instanceof ApiRequestError) {
      errorMessage.value = error.message;
      errorRequestId.value = error.requestId;
    } else {
      errorMessage.value = "Unable to load dashboard data.";
    }
  } finally {
    loading.value = false;
  }
}

async function onTriggerRun(): Promise<void> {
  errorMessage.value = null;
  errorRequestId.value = null;
  successMessage.value = null;

  try {
    const result = await runStatus.startManualCheck();
    if (result.kind === "started") {
      successMessage.value = `Update check #${result.runId} started successfully.`;
      await loadSnapshot();
      return;
    }

    if (result.kind === "already_running") {
      successMessage.value = result.runId
        ? `Update check #${result.runId} is already in progress.`
        : "An update check is already in progress.";
      await loadSnapshot();
      return;
    }

    errorRequestId.value = result.requestId;
  } catch {
    errorMessage.value = "Unable to start an update check.";
    errorRequestId.value = null;
  }
}

async function onCancelRun(): Promise<void> {
  if (!activeRunId.value) return;
  errorMessage.value = null;
  errorRequestId.value = null;
  successMessage.value = null;
  isCancelAnimating.value = true;

  try {
    const result = await runStatus.cancelActiveCheck();
    if (result.kind === "success") {
      successMessage.value = "Update check canceled successfully.";
      await loadSnapshot();
    } else {
      errorMessage.value = result.message;
    }
  } catch {
    errorMessage.value = "Unable to cancel the update check.";
  } finally {
    isCancelAnimating.value = false;
  }
}

onMounted(() => {
  startRunStatusSyncLoop();
  void loadSnapshot();
  void runStatus.syncLatest();
});

onUnmounted(() => {
  stopRunStatusSyncLoop();
});

watch(
  () => runStatus.latestRun,
  (nextRun, previousRun) => {
    if (refreshingAfterCompletion.value) {
      return;
    }
    if (!shouldRefreshAfterRunChange(nextRun, previousRun)) {
      return;
    }

    refreshingAfterCompletion.value = true;
    void loadSnapshot().finally(() => {
      refreshingAfterCompletion.value = false;
    });
  },
);
</script>

<template>
  <AppPage
    title="Dashboard"
    subtitle="Track recent publication updates and monitor background profile checks."
    fill
  >
    <div class="flex min-h-0 flex-1 flex-col gap-4 xl:overflow-hidden">
      <RequestStateAlerts
        :success-message="successMessage"
        :error-message="errorMessage"
        :error-request-id="errorRequestId"
        error-title="Dashboard request failed"
        @dismiss-success="successMessage = null"
      />
      <ScrapeSafetyBanner
        :safety-state="runStatus.safetyState"
        :manual-run-allowed="userSettings.manualRunAllowed"
      />

      <div class="h-0 min-h-0 flex-1 xl:overflow-hidden">
        <AsyncStateGate :loading="loading" :loading-lines="6" :show-empty="false">
          <template v-if="snapshot">
            <section class="grid min-h-0 gap-4 xl:h-full xl:grid-cols-2 xl:grid-rows-[minmax(0,1fr)] xl:overflow-hidden">
              <AppCard class="flex min-h-0 flex-col gap-4 xl:h-full xl:overflow-hidden">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex items-center gap-1">
                      <h2 class="text-lg font-semibold text-ink-primary">Recent Publications</h2>
                      <AppHelpHint
                        text="Recent publications are paper records discovered from tracked scholar profile checks."
                      />
                    </div>
                    <p class="text-sm text-secondary">Newest papers found while checking your tracked scholar profiles.</p>
                  </div>
                  <RouterLink
                    to="/publications"
                    class="inline-flex min-h-10 items-center justify-center rounded-lg border border-action-secondary-border bg-action-secondary-bg px-3 py-2 text-sm font-semibold text-action-secondary-text shadow-sm transition hover:border-action-secondary-hover-border hover:bg-action-secondary-hover-bg hover:text-action-secondary-hover-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-focus-offset"
                  >
                    view all
                  </RouterLink>
                </div>

                <AppEmptyState
                  v-if="recentPublications.length === 0"
                  title="No new publications"
                  body="When a completed update check discovers changes, they will appear here."
                />

                <ul v-else class="grid min-h-0 flex-1 content-start gap-3 overflow-y-scroll overscroll-contain pr-1">
                  <li
                    v-for="item in recentPublications.slice(0, 20)"
                    :key="item.publication_id"
                    class="grid gap-1 rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2"
                  >
                    <a
                      v-if="item.pub_url"
                      :href="item.pub_url"
                      target="_blank"
                      rel="noreferrer"
                      class="link-inline text-sm font-semibold text-ink-primary"
                    >
                      {{ item.title }}
                    </a>
                    <span v-else class="text-sm font-semibold text-ink-primary">{{ item.title }}</span>
                    <div class="flex flex-wrap items-center gap-3 text-xs text-secondary">
                      <RouterLink
                        :to="{ name: 'publications', query: { scholar: String(item.scholar_profile_id) } }"
                        class="link-inline"
                      >
                        {{ item.scholar_label }}
                      </RouterLink>
                      <span>First seen: {{ formatDate(item.first_seen_at) }}</span>
                    </div>
                  </li>
                </ul>
              </AppCard>

              <div class="grid min-h-0 gap-4 xl:h-full xl:grid-rows-[auto_minmax(0,1fr)] xl:overflow-hidden">
                <AppCard class="flex min-h-0 flex-col gap-4">
                  <div class="flex items-center gap-1">
                    <h2 class="text-lg font-semibold text-ink-primary">Overview</h2>
                    <AppHelpHint text="Shared summary metrics for publication coverage and processing pressure." />
                  </div>
                  <div class="grid gap-3 sm:grid-cols-2">
                    <article class="rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2">
                      <p class="text-xs uppercase tracking-wide text-ink-muted">Tracked publications</p>
                      <p class="mt-1 text-xl font-semibold text-ink-primary">{{ snapshot.totalCount }}</p>
                    </article>
                    <article class="rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2">
                      <p class="text-xs uppercase tracking-wide text-ink-muted">New from latest check</p>
                      <p class="mt-1 text-xl font-semibold text-ink-primary">{{ snapshot.newCount }}</p>
                    </article>
                    <article class="rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2">
                      <p class="text-xs uppercase tracking-wide text-ink-muted">Scholars processed</p>
                      <p class="mt-1 text-xl font-semibold text-ink-primary">{{ displayedLatestRun?.scholar_count ?? 0 }}</p>
                    </article>
                    <article class="rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2">
                      <p class="text-xs uppercase tracking-wide text-ink-muted">Queue pressure</p>
                      <p class="mt-1 text-xl font-semibold text-ink-primary">
                        {{ snapshot.queue.queued + snapshot.queue.retrying + snapshot.queue.dropped }}
                      </p>
                    </article>
                  </div>
                  <QueueHealthBadge
                    :queued="snapshot.queue.queued"
                    :retrying="snapshot.queue.retrying"
                    :dropped="snapshot.queue.dropped"
                  />
                </AppCard>

                <AppCard class="flex min-h-0 flex-col gap-4 xl:overflow-hidden">
                  <div class="flex flex-wrap items-center justify-between gap-2">
                    <div class="flex items-center gap-1">
                      <h2 class="text-lg font-semibold text-ink-primary">Activity Monitor</h2>
                      <AppHelpHint
                        text="Combines latest profile-check details with recent check history so you can spot issues quickly."
                      />
                    </div>
                    <div class="flex items-center gap-2">
                      <AppButton
                        v-if="runStatus.isLikelyRunning"
                        variant="danger"
                        :disabled="!activeRunId || isCancelAnimating"
                        @click="onCancelRun"
                      >
                        <span class="inline-flex items-center gap-2">
                          <span v-if="isCancelAnimating" class="relative inline-flex h-2.5 w-2.5">
                            <span
                              class="absolute inline-flex h-full w-full animate-ping rounded-full bg-current opacity-60"
                            />
                            <span class="relative inline-flex h-2.5 w-2.5 rounded-full bg-current" />
                          </span>
                          Cancel check
                        </span>
                      </AppButton>
                      <AppButton
                        :disabled="isStartBlocked"
                        :title="startCheckDisabledReason || undefined"
                        :class="isStartCheckAnimating ? 'shadow-[0_0_0_1px_var(--color-state-info-border)]' : ''"
                        @click="onTriggerRun"
                      >
                        <span class="inline-flex items-center gap-2">
                          <span v-if="isStartCheckAnimating" class="relative inline-flex h-2.5 w-2.5">
                            <span
                              class="absolute inline-flex h-full w-full animate-ping rounded-full bg-current opacity-60"
                            />
                            <span class="relative inline-flex h-2.5 w-2.5 rounded-full bg-current" />
                          </span>
                          {{ startCheckLabel }}
                        </span>
                      </AppButton>
                      <RouterLink
                        v-if="auth.isAdmin"
                        to="/admin/runs"
                        class="link-inline text-sm"
                      >
                        Open check history
                      </RouterLink>
                    </div>
                  </div>

                  <div
                    v-if="showRunningHint"
                    class="rounded-xl border border-state-info-border bg-state-info-bg px-3 py-2"
                  >
                    <div class="flex flex-wrap items-center justify-between gap-2">
                      <div class="flex items-center gap-2">
                        <RunStatusBadge status="running" />
                        <span class="text-sm font-semibold text-state-info-text">Check in progress</span>
                      </div>
                      <RouterLink
                        v-if="auth.isAdmin && activeRunId"
                        :to="`/admin/runs/${activeRunId}`"
                        class="link-inline text-xs"
                      >
                        View live check details
                      </RouterLink>
                    </div>
                    <p class="mt-1 text-sm text-state-info-text">
                      A publication check has started. Results and counts update after it finishes.
                    </p>
                  </div>

                  <div
                    v-if="displayedLatestRun"
                    class="rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2"
                  >
                    <div class="flex flex-wrap items-center justify-between gap-2">
                      <div class="flex items-center gap-2">
                        <RunStatusBadge :status="displayedLatestRun.status" />
                        <span class="text-sm font-semibold text-ink-primary">Latest check #{{ displayedLatestRun.id }}</span>
                      </div>
                      <RouterLink
                        v-if="auth.isAdmin"
                        :to="`/admin/runs/${displayedLatestRun.id}`"
                        class="link-inline text-xs"
                      >
                        View diagnostics
                      </RouterLink>
                    </div>
                    <p class="mt-2 text-sm text-secondary">
                      Started {{ formatDate(displayedLatestRun.start_dt) }}.
                      <template v-if="runStatus.isLikelyRunning">
                        {{ runStatus.scholarProgress?.visited ?? 0 }} / {{ runStatus.scholarProgress?.total ?? '…' }} visited
                        · {{ runStatus.scholarProgress?.finished ?? 0 }} finished
                        · {{ displayedLatestRun.new_publication_count }} new publications.
                      </template>
                      <template v-else>
                        Processed {{ displayedLatestRun.scholar_count }} scholars and discovered
                        {{ displayedLatestRun.new_publication_count }} new publications.
                      </template>
                    </p>
                  </div>
                  <AppEmptyState
                    v-else
                    title="No checks recorded"
                    body="Start an update check to begin monitoring activity."
                  />

                  <div class="flex min-h-0 flex-1 flex-col gap-2 xl:overflow-hidden">
                    <p class="text-xs font-semibold uppercase tracking-wide text-muted">Recent checks</p>
                    <AppEmptyState
                      v-if="recentRuns.length === 0"
                      title="No checks yet"
                      body="Check history will appear here."
                    />
                    <ul v-else class="grid min-h-0 flex-1 content-start gap-2 overflow-y-auto overscroll-contain pr-1">
                      <li
                        v-for="run in recentRuns"
                        :key="run.id"
                        class="grid gap-1 rounded-xl border border-stroke-default bg-surface-card-muted px-3 py-2"
                      >
                        <div class="flex items-center justify-between gap-2">
                          <div class="flex items-center gap-2">
                            <RunStatusBadge :status="run.status" />
                            <span class="text-sm text-secondary">Check #{{ run.id }}</span>
                          </div>
                          <RouterLink v-if="auth.isAdmin" :to="`/admin/runs/${run.id}`" class="link-inline text-xs">
                            Details
                          </RouterLink>
                        </div>
                        <span class="text-xs text-secondary">
                          {{ formatDate(run.start_dt) }} • {{ run.new_publication_count }} new
                        </span>
                      </li>
                    </ul>
                  </div>
                </AppCard>
              </div>
            </section>
          </template>
        </AsyncStateGate>
      </div>
    </div>
  </AppPage>
</template>
