<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import PageTitle from "@/components/global/PageTitle.vue";
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import SuperUserDashboardNavigation from "@/views/dashboards/superUserDashboard/SuperUserDashboardNavigation.vue";
import {
  deployCronWorkers,
  listCronRuns,
  listCronTasks,
  listCronWorkers,
  runCronTask,
  updateCronTask,
} from "@/services/superuserCron.js";

const { t, locale } = useI18n();
const RUN_POLL_INTERVAL_MS = 2500;
const RUN_POLL_ATTEMPTS = 6;
const WORKER_POLL_INTERVAL_MS = 5000;
const WORKER_POLL_ATTEMPTS = 24;
const IN_PROGRESS_STATUSES = new Set(["queued", "running"]);
const WORKER_PENDING_STATES = new Set(["deploying", "waiting_for_heartbeat"]);

const tasks = ref([]);
const runs = ref([]);
const workers = ref([]);
const workerStatus = ref(null);
const workerChannels = ref([]);
const workerSummary = ref({ total: 0, running: 0, stale: 0 });
const workerDeployment = ref(null);
const selectedWorkerChannelId = ref(null);
const summary = ref({ total: 0, enabled: 0, due: 0 });
const intervalDrafts = ref({});
const loading = ref(false);
const runsLoading = ref(false);
const workersLoading = ref(false);
const errorMessage = ref("");
const queuedMessage = ref("");
const busyTaskId = ref(null);
const savingTaskId = ref(null);
const deployingWorker = ref(false);
const pollTimeouts = new Set();

const canView = computed(() => SessionUser.canAccessSuperUser() || SessionUser.hasPermission("superuser_cron_view"));
const canRun = computed(() => SessionUser.canAccessSuperUser() || SessionUser.hasPermission("SUPERUSER_RUN_CRON"));
const canManage = computed(
  () => SessionUser.canAccessSuperUser() || SessionUser.hasPermission("superuser_cron_manage")
);
const canDeployWorkers = computed(
  () => canManage.value && (SessionUser.canAccessSuperUser() || SessionUser.hasPermission("superuser_coolify_manage"))
);
const enabledTasks = computed(() => tasks.value.filter((task) => task.enabled));
const dueTasks = computed(() => enabledTasks.value.filter((task) => task.due));
const nextTask = computed(() => {
  return (
    [...enabledTasks.value]
      .filter((task) => task.next_run_at)
      .sort((left, right) => Date.parse(left.next_run_at) - Date.parse(right.next_run_at))[0] || null
  );
});
const workerTargetLabel = computed(
  () =>
    workerStatus.value?.cron_target?.coolify_service_uuid ||
    workerStatus.value?.cron_target?.coolify_resource_uuid ||
    workerStatus.value?.cron_target?.id ||
    workerDeployment.value?.target?.coolify_service_uuid ||
    workerDeployment.value?.target?.coolify_resource_uuid ||
    workerDeployment.value?.target?.id ||
    t("cron.empty_value")
);
const hasWorkerDeploymentTarget = computed(
  () =>
    Boolean(workerStatus.value?.cron_target?.id) ||
    Boolean(workerDeployment.value?.target?.id) ||
    Boolean(workerStatus.value?.cron_target?.coolify_service_uuid) ||
    Boolean(workerDeployment.value?.target?.coolify_service_uuid) ||
    Boolean(workerDeployment.value?.target?.coolify_resource_uuid)
);
const workerDeploymentAction = computed(
  () =>
    workerStatus.value?.deployment?.action ||
    workerDeployment.value?.action ||
    (hasWorkerDeploymentTarget.value ? "update" : "create")
);
const workerDeploymentCanDeploy = computed(() => {
  const canDeploy = workerStatus.value?.deployment?.can_deploy ?? workerDeployment.value?.can_deploy;
  return canDeploy === undefined || canDeploy === null ? true : Boolean(canDeploy);
});
const workerDeploymentActionLabel = computed(() => {
  if (workerDeploymentAction.value === "repair") {
    return t("cron.workers.repair_deployment");
  }
  if (workerDeploymentAction.value === "update") {
    return t("cron.workers.update_deployment");
  }
  return t("cron.workers.deploy_to_coolify");
});
const workerState = computed(() => workerStatus.value?.state || workerDeployment.value?.state || "unknown");
const workerIssues = computed(() => (Array.isArray(workerStatus.value?.issues) ? workerStatus.value.issues : []));
const workerLatestDeployment = computed(() => workerStatus.value?.latest_deployment || workerDeployment.value?.latest_deployment || null);
const workerRecentDeployments = computed(() =>
  Array.isArray(workerStatus.value?.recent_deployments) ? workerStatus.value.recent_deployments : []
);
const selectedWorkerChannel = computed(
  () =>
    workerChannels.value.find((channel) => Number(channel.id) === Number(selectedWorkerChannelId.value)) ||
    workerStatus.value?.channel ||
    null
);
const workerApiTargetLabel = computed(
  () =>
    workerStatus.value?.api_target?.id ||
    workerDeployment.value?.api_target?.id ||
    t("cron.empty_value")
);

const responseData = (response, fallback) => response?.data?.data ?? fallback;
const parseError = (error) =>
  error?.response?.data?.data?.message || error?.response?.data?.message || error?.message || t("cron.errors.generic");

function seedIntervalDrafts(nextTasks) {
  const nextDrafts = {};
  nextTasks.forEach((task) => {
    nextDrafts[task.id] = Number(task?.schedule?.seconds || task?.default_schedule?.seconds || 60);
  });
  intervalDrafts.value = nextDrafts;
}

async function loadTasks() {
  const data = responseData(await listCronTasks(), {});
  const nextTasks = Array.isArray(data.tasks) ? data.tasks : [];
  tasks.value = nextTasks;
  summary.value = data.summary || {
    total: nextTasks.length,
    enabled: enabledTasks.value.length,
    due: dueTasks.value.length,
  };
  seedIntervalDrafts(nextTasks);
}

async function loadRuns() {
  runsLoading.value = true;
  try {
    const data = responseData(await listCronRuns({ limit: 25 }), {});
    runs.value = Array.isArray(data.runs) ? data.runs : [];
  } finally {
    runsLoading.value = false;
  }
}

async function loadWorkers() {
  workersLoading.value = true;
  try {
    const data = responseData(
      await listCronWorkers({
        channelId: selectedWorkerChannelId.value,
        includeProvider: true,
      }),
      {}
    );
    workers.value = Array.isArray(data.workers) ? data.workers : [];
    workerSummary.value = data.summary || { total: workers.value.length, running: 0, stale: 0 };
    workerDeployment.value = data.deployment || null;
    workerStatus.value = data || null;
    workerChannels.value = Array.isArray(data.channels) ? data.channels : workerChannels.value;
    if (!selectedWorkerChannelId.value && data.channel?.id) {
      selectedWorkerChannelId.value = data.channel.id;
    }
  } finally {
    workersLoading.value = false;
  }
}

async function refreshCron() {
  loading.value = true;
  errorMessage.value = "";
  try {
    await Promise.all([loadTasks(), loadRuns(), loadWorkers()]);
  } catch (error) {
    errorMessage.value = parseError(error);
  } finally {
    loading.value = false;
  }
}

async function runTask(task) {
  busyTaskId.value = task.id;
  errorMessage.value = "";
  queuedMessage.value = "";
  try {
    const data = responseData(await runCronTask(task.id, { force: false }), null);
    if (data) {
      runs.value = [data, ...runs.value].slice(0, 25);
      queuedMessage.value = t("cron.messages.queued", { task: task.name || task.id });
    }
    await Promise.all([loadTasks(), loadRuns()]);
    void pollQueuedRun(data);
  } catch (error) {
    errorMessage.value = parseError(error);
  } finally {
    busyTaskId.value = null;
  }
}

async function deployWorkers() {
  deployingWorker.value = true;
  errorMessage.value = "";
  queuedMessage.value = "";
  const updatingDeployment = hasWorkerDeploymentTarget.value;
  try {
    const channelId = selectedWorkerChannelId.value || workerStatus.value?.channel?.id || null;
    const data = responseData(
      await deployCronWorkers({
        ...(channelId ? { channel_id: channelId } : {}),
      }),
      {}
    );
    if (data.worker_status) {
      applyWorkerStatus(data.worker_status);
    }
    await loadWorkers();
    const deploymentId = data.deployment?.id || data.applied?.[0]?.deployment_id || null;
    queuedMessage.value = updatingDeployment
      ? t("cron.messages.worker_update_queued", { id: deploymentId || t("cron.empty_value") })
      : t("cron.messages.worker_deploy_queued", { id: deploymentId || t("cron.empty_value") });
    void pollWorkerDeployment();
  } catch (error) {
    errorMessage.value = parseError(error);
  } finally {
    deployingWorker.value = false;
  }
}

function applyWorkerStatus(data) {
  workers.value = Array.isArray(data.workers) ? data.workers : [];
  workerSummary.value = data.summary || { total: workers.value.length, running: 0, stale: 0 };
  workerDeployment.value = data.deployment || null;
  workerStatus.value = data || null;
  workerChannels.value = Array.isArray(data.channels) ? data.channels : workerChannels.value;
  if (!selectedWorkerChannelId.value && data.channel?.id) {
    selectedWorkerChannelId.value = data.channel.id;
  }
}

async function pollWorkerDeployment() {
  try {
    for (let attempt = 0; attempt < WORKER_POLL_ATTEMPTS; attempt += 1) {
      await waitForPollDelay(WORKER_POLL_INTERVAL_MS);
      await loadWorkers();
      if (!WORKER_PENDING_STATES.has(workerState.value)) {
        return;
      }
    }
  } catch (error) {
    errorMessage.value = parseError(error);
  }
}

function waitForPollDelay(delay = RUN_POLL_INTERVAL_MS) {
  return new Promise((resolve) => {
    const timeout = window.setTimeout(() => {
      pollTimeouts.delete(timeout);
      resolve();
    }, delay);
    pollTimeouts.add(timeout);
  });
}

async function pollQueuedRun(run) {
  const runId = run?.id;
  if (!runId || !IN_PROGRESS_STATUSES.has(String(run?.status || ""))) {
    return;
  }

  try {
    for (let attempt = 0; attempt < RUN_POLL_ATTEMPTS; attempt += 1) {
      await waitForPollDelay();
      await Promise.all([loadTasks(), loadRuns()]);
      const currentRun = runs.value.find((candidate) => Number(candidate.id) === Number(runId));
      if (!currentRun || !IN_PROGRESS_STATUSES.has(String(currentRun.status || ""))) {
        return;
      }
    }
  } catch (error) {
    errorMessage.value = parseError(error);
  }
}

async function toggleTask(task) {
  savingTaskId.value = task.id;
  errorMessage.value = "";
  try {
    const data = responseData(await updateCronTask(task.id, { enabled: !task.enabled }), {});
    if (Array.isArray(data.tasks)) {
      tasks.value = data.tasks;
      summary.value = data.summary || summary.value;
      seedIntervalDrafts(data.tasks);
    } else {
      await loadTasks();
    }
  } catch (error) {
    errorMessage.value = parseError(error);
  } finally {
    savingTaskId.value = null;
  }
}

async function saveInterval(task) {
  const seconds = Math.max(30, Number.parseInt(intervalDrafts.value[task.id], 10) || task?.schedule?.seconds || 60);
  intervalDrafts.value = {
    ...intervalDrafts.value,
    [task.id]: seconds,
  };
  savingTaskId.value = task.id;
  errorMessage.value = "";
  try {
    const data = responseData(
      await updateCronTask(task.id, {
        schedule: {
          type: "interval",
          seconds,
        },
      }),
      {}
    );
    if (Array.isArray(data.tasks)) {
      tasks.value = data.tasks;
      summary.value = data.summary || summary.value;
      seedIntervalDrafts(data.tasks);
    } else {
      await loadTasks();
    }
  } catch (error) {
    errorMessage.value = parseError(error);
  } finally {
    savingTaskId.value = null;
  }
}

function formatDate(value) {
  if (!value) {
    return t("cron.empty_value");
  }
  const timestamp = Date.parse(String(value).replace(" ", "T"));
  if (!Number.isFinite(timestamp)) {
    return String(value);
  }
  return new Intl.DateTimeFormat(locale.value, {
    dateStyle: "short",
    timeStyle: "medium",
  }).format(new Date(timestamp));
}

function formatDuration(milliseconds) {
  const value = Number(milliseconds || 0);
  if (!Number.isFinite(value) || value <= 0) {
    return t("cron.empty_value");
  }
  if (value < 1000) {
    return t("cron.duration.ms", { value });
  }
  return t("cron.duration.seconds", { value: (value / 1000).toFixed(1) });
}

function scheduleLabel(task) {
  const seconds = Number(task?.schedule?.seconds || 0);
  if (seconds <= 0) {
    return t("cron.empty_value");
  }
  if (seconds % 3600 === 0) {
    return t("cron.interval.hours", { value: seconds / 3600 });
  }
  if (seconds % 60 === 0) {
    return t("cron.interval.minutes", { value: seconds / 60 });
  }
  return t("cron.interval.seconds", { value: seconds });
}

function statusLabel(status) {
  if (status === "deployed") {
    return t("cron.status.deployed");
  }
  if (status === "deploying") {
    return t("cron.status.deploying");
  }
  if (status === "failed") {
    return t("cron.status.failed");
  }
  if (status === "healthy") {
    return t("cron.status.healthy");
  }
  if (status === "degraded") {
    return t("cron.status.degraded");
  }
  if (status === "needs_deploy") {
    return t("cron.status.needs_deploy");
  }
  if (status === "queued") {
    return t("cron.status.queued");
  }
  if (status === "running") {
    return t("cron.status.running");
  }
  if (status === "skipped") {
    return t("cron.status.skipped");
  }
  if (status === "succeeded") {
    return t("cron.status.succeeded");
  }
  if (status === "timed_out") {
    return t("cron.status.timed_out");
  }
  if (status === "waiting_for_heartbeat") {
    return t("cron.status.waiting_for_heartbeat");
  }
  return status || t("cron.status.unknown");
}

function statusClass(status) {
  if (status === "succeeded" || status === "healthy" || status === "deployed") {
    return "is-success";
  }
  if (status === "failed" || status === "timed_out") {
    return "is-danger";
  }
  if (status === "running" || status === "deploying" || status === "waiting_for_heartbeat") {
    return "is-info";
  }
  if (status === "queued" || status === "degraded" || status === "needs_deploy") {
    return "is-warning";
  }
  return "is-light";
}

function issueClass(issue) {
  if (issue?.severity === "danger") {
    return "is-danger";
  }
  if (issue?.severity === "warning") {
    return "is-warning";
  }
  return "is-info";
}

function deploymentLabel(deployment) {
  if (!deployment) {
    return t("cron.empty_value");
  }
  const id = deployment.provider_operation_id || deployment.id || t("cron.empty_value");
  return t("cron.workers.deployment_label", {
    id,
    status: statusLabel(deployment.status),
  });
}

async function changeWorkerChannel() {
  errorMessage.value = "";
  try {
    await loadWorkers();
  } catch (error) {
    errorMessage.value = parseError(error);
  }
}

function runStartedLabel(run) {
  return formatDate(run.started_at || run.scheduled_for || run.created_at);
}

onMounted(() => {
  void refreshCron();
});

onBeforeUnmount(() => {
  pollTimeouts.forEach((timeout) => window.clearTimeout(timeout));
  pollTimeouts.clear();
});
</script>

<template>
  <RestrictedPageWrapper :hasPermission="canView">
    <SuperUserDashboardNavigation />
    <PageTitle :title="t('cron.title')" :subtitle="t('cron.subtitle')" />

    <section class="cron-page" data-testid="superuser-cron-page">
      <div class="cron-toolbar">
        <div class="cron-stats" aria-live="polite">
          <div class="cron-stat" data-testid="cron-total-tasks">
            <span>{{ t("cron.summary.total") }}</span>
            <strong>{{ summary.total ?? tasks.length }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-enabled-tasks">
            <span>{{ t("cron.summary.enabled") }}</span>
            <strong>{{ summary.enabled ?? enabledTasks.length }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-due-tasks">
            <span>{{ t("cron.summary.due") }}</span>
            <strong>{{ summary.due ?? dueTasks.length }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-next-task">
            <span>{{ t("cron.summary.next") }}</span>
            <strong>{{ nextTask ? formatDate(nextTask.next_run_at) : t("cron.empty_value") }}</strong>
          </div>
        </div>
        <button
          class="button is-light"
          type="button"
          :class="{ 'is-loading': loading }"
          @click="refreshCron"
          data-testid="cron-refresh"
        >
          {{ t("cron.actions.refresh") }}
        </button>
      </div>

      <div v-if="errorMessage" class="notification is-danger is-light" data-testid="cron-error">
        {{ errorMessage }}
      </div>
      <div v-if="queuedMessage" class="notification is-info is-light" data-testid="cron-run-queued">
        {{ queuedMessage }}
      </div>

      <div class="table-container cron-table-container">
        <table class="table is-fullwidth is-hoverable cron-table" data-testid="cron-task-table">
          <thead>
            <tr>
              <th>{{ t("cron.table.task") }}</th>
              <th>{{ t("cron.table.module") }}</th>
              <th>{{ t("cron.table.schedule") }}</th>
              <th>{{ t("cron.table.next_run") }}</th>
              <th>{{ t("cron.table.estimate") }}</th>
              <th>{{ t("cron.table.status") }}</th>
              <th>{{ t("cron.table.enabled") }}</th>
              <th>{{ t("cron.table.actions") }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-if="loading && tasks.length === 0">
              <td colspan="8">{{ t("cron.loading") }}</td>
            </tr>
            <tr v-else-if="tasks.length === 0">
              <td colspan="8">{{ t("cron.empty") }}</td>
            </tr>
            <tr v-for="task in tasks" :key="task.id" :data-testid="`cron-task-row-${task.id}`">
              <td>
                <strong>{{ task.name }}</strong>
                <small>{{ task.description }}</small>
              </td>
              <td>{{ task.module }}</td>
              <td>
                <div class="cron-interval-control">
                  <input
                    v-model.number="intervalDrafts[task.id]"
                    class="input is-small"
                    type="number"
                    min="30"
                    step="30"
                    :disabled="!canManage || savingTaskId === task.id"
                    :aria-label="t('cron.actions.edit_interval')"
                    :data-testid="`cron-task-interval-${task.id}`"
                  />
                  <button
                    class="button is-small is-light"
                    type="button"
                    :disabled="!canManage || savingTaskId === task.id"
                    :class="{ 'is-loading': savingTaskId === task.id }"
                    @click="saveInterval(task)"
                    :data-testid="`cron-task-save-${task.id}`"
                  >
                    {{ t("cron.actions.save") }}
                  </button>
                </div>
                <small>{{ scheduleLabel(task) }}</small>
              </td>
              <td>
                <span>{{ formatDate(task.next_run_at) }}</span>
                <small v-if="task.due">{{ t("cron.states.due_now") }}</small>
              </td>
              <td>{{ formatDuration(task.estimated_duration_ms) }}</td>
              <td>
                <span class="tag" :class="statusClass(task.last_status)">
                  {{ statusLabel(task.last_status) }}
                </span>
                <small v-if="task.last_error">{{ task.last_error }}</small>
              </td>
              <td>
                <label class="cron-toggle">
                  <input
                    type="checkbox"
                    :checked="task.enabled"
                    :disabled="!canManage || savingTaskId === task.id"
                    @change="toggleTask(task)"
                    :data-testid="`cron-task-toggle-${task.id}`"
                  />
                  <span>{{ task.enabled ? t("cron.states.enabled") : t("cron.states.disabled") }}</span>
                </label>
              </td>
              <td>
                <button
                  class="button is-small is-primary"
                  type="button"
                  :disabled="!canRun || busyTaskId === task.id"
                  :class="{ 'is-loading': busyTaskId === task.id }"
                  @click="runTask(task)"
                  :data-testid="`cron-task-run-${task.id}`"
                >
                  {{ t("cron.actions.run_now") }}
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <section class="cron-worker-panel" data-testid="cron-worker-panel">
        <div class="cron-section-heading">
          <div>
            <h2>{{ t("cron.workers.title") }}</h2>
            <small data-testid="cron-worker-channel">
              {{ selectedWorkerChannel?.slug || t("cron.empty_value") }}
            </small>
          </div>
          <div class="cron-worker-controls">
            <select
              v-if="workerChannels.length > 1"
              v-model.number="selectedWorkerChannelId"
              class="select is-small"
              data-testid="cron-worker-channel-select"
              @change="changeWorkerChannel"
            >
              <option v-for="channel in workerChannels" :key="channel.id" :value="channel.id">
                {{ channel.slug }}
              </option>
            </select>
            <button
              class="button is-small is-light"
              type="button"
              :disabled="!canDeployWorkers || deployingWorker || !workerDeploymentCanDeploy"
              :class="{ 'is-loading': deployingWorker }"
              @click="deployWorkers"
              data-testid="cron-worker-deploy"
            >
              {{ workerDeploymentActionLabel }}
            </button>
          </div>
        </div>
        <div v-if="workerIssues.length > 0" class="cron-worker-issues" data-testid="cron-worker-issues">
          <span
            v-for="issue in workerIssues"
            :key="issue.code || issue.message"
            class="tag"
            :class="issueClass(issue)"
          >
            {{ issue.message }}
          </span>
        </div>
        <div class="cron-stats" aria-live="polite">
          <div class="cron-stat" data-testid="cron-worker-total">
            <span>{{ t("cron.workers.total") }}</span>
            <strong>{{ workerSummary.total ?? workers.length }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-running">
            <span>{{ t("cron.workers.running") }}</span>
            <strong>{{ workerSummary.running ?? 0 }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-stale">
            <span>{{ t("cron.workers.stale") }}</span>
            <strong>{{ workerSummary.stale ?? 0 }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-state">
            <span>{{ t("cron.workers.state") }}</span>
            <strong>
              <span class="tag" :class="statusClass(workerState)">
                {{ statusLabel(workerState) }}
              </span>
            </strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-target">
            <span>{{ t("cron.workers.target") }}</span>
            <strong>{{ workerTargetLabel }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-api-target">
            <span>{{ t("cron.workers.api_target") }}</span>
            <strong>{{ workerApiTargetLabel }}</strong>
          </div>
          <div class="cron-stat" data-testid="cron-worker-latest-deployment">
            <span>{{ t("cron.workers.latest_deployment") }}</span>
            <strong>{{ deploymentLabel(workerLatestDeployment) }}</strong>
          </div>
        </div>
        <div v-if="workerRecentDeployments.length > 0" class="cron-worker-deployments">
          <table class="table is-fullwidth is-striped is-narrow" data-testid="cron-worker-deployments">
            <thead>
              <tr>
                <th>{{ t("cron.workers.deployment") }}</th>
                <th>{{ t("cron.history.status") }}</th>
                <th>{{ t("cron.history.started") }}</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="deployment in workerRecentDeployments" :key="deployment.id">
                <td>{{ deployment.provider_operation_id || deployment.id }}</td>
                <td><span class="tag" :class="statusClass(deployment.status)">{{ statusLabel(deployment.status) }}</span></td>
                <td>{{ formatDate(deployment.started_at || deployment.created_at) }}</td>
              </tr>
            </tbody>
          </table>
        </div>
        <div class="table-container cron-table-container">
          <table class="table is-fullwidth is-striped">
            <thead>
              <tr>
                <th>{{ t("cron.workers.name") }}</th>
                <th>{{ t("cron.workers.source") }}</th>
                <th>{{ t("cron.workers.status") }}</th>
                <th>{{ t("cron.workers.heartbeat") }}</th>
                <th>{{ t("cron.workers.last_run_count") }}</th>
              </tr>
            </thead>
            <tbody>
              <tr v-if="workersLoading && workers.length === 0">
                <td colspan="5">{{ t("cron.workers.loading") }}</td>
              </tr>
              <tr v-else-if="workers.length === 0">
                <td colspan="5">
                  {{
                    hasWorkerDeploymentTarget
                      ? t("cron.workers.empty_with_target")
                      : t("cron.workers.empty")
                  }}
                </td>
              </tr>
              <tr v-for="worker in workers" :key="worker.worker_id">
                <td>
                  <strong>{{ worker.name || worker.worker_id }}</strong>
                  <small>{{ worker.hostname || t("cron.empty_value") }}</small>
                </td>
                <td>{{ worker.source }}</td>
                <td>
                  <span class="tag" :class="statusClass(worker.status)">
                    {{ statusLabel(worker.status) }}
                  </span>
                  <small v-if="worker.stale">{{ t("cron.workers.stale") }}</small>
                </td>
                <td>{{ formatDate(worker.last_heartbeat_at) }}</td>
                <td>{{ worker.last_run_count ?? 0 }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </section>

      <section class="cron-history" data-testid="cron-run-history">
        <div class="cron-section-heading">
          <h2>{{ t("cron.history.title") }}</h2>
          <button
            class="button is-small is-light"
            type="button"
            :class="{ 'is-loading': runsLoading }"
            @click="loadRuns"
          >
            {{ t("cron.actions.refresh") }}
          </button>
        </div>
        <div class="table-container">
          <table class="table is-fullwidth is-striped">
            <thead>
              <tr>
                <th>{{ t("cron.history.task") }}</th>
                <th>{{ t("cron.history.source") }}</th>
                <th>{{ t("cron.history.status") }}</th>
                <th>{{ t("cron.history.started") }}</th>
                <th>{{ t("cron.history.duration") }}</th>
              </tr>
            </thead>
            <tbody>
              <tr v-if="runs.length === 0">
                <td colspan="5">{{ t("cron.history.empty") }}</td>
              </tr>
              <tr v-for="run in runs" :key="run.id || `${run.task_id}-${run.started_at}`">
                <td>{{ run.task_id }}</td>
                <td>{{ run.source }}</td>
                <td>
                  <span class="tag" :class="statusClass(run.status)">{{ statusLabel(run.status) }}</span>
                </td>
                <td>{{ runStartedLabel(run) }}</td>
                <td>{{ formatDuration(run.duration_ms) }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </section>
    </section>
  </RestrictedPageWrapper>
</template>

<style scoped>
.cron-page {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 0 1rem 2rem;
}

.cron-toolbar,
.cron-section-heading {
  align-items: center;
  display: flex;
  gap: 1rem;
  justify-content: space-between;
}

.cron-stats {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(4, minmax(8rem, 1fr));
  width: 100%;
}

.cron-stat {
  border: 1px solid #d7dde5;
  border-radius: 6px;
  padding: 0.75rem;
}

.cron-stat span,
.cron-table small {
  color: #667085;
  display: block;
  font-size: 0.8rem;
}

.cron-stat strong {
  display: block;
  font-size: 1rem;
  margin-top: 0.25rem;
  overflow-wrap: anywhere;
}

.cron-section-heading small {
  color: #667085;
  display: block;
  font-size: 0.8rem;
  margin-top: 0.2rem;
}

.cron-worker-controls,
.cron-worker-issues {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.cron-worker-controls select {
  min-width: 8rem;
}

.cron-worker-issues {
  margin-bottom: 0.75rem;
}

.cron-worker-deployments {
  margin: 0.75rem 0;
}

.cron-table-container {
  border: 1px solid #d7dde5;
  border-radius: 6px;
}

.cron-table td {
  vertical-align: middle;
}

.cron-interval-control {
  align-items: center;
  display: flex;
  gap: 0.5rem;
  min-width: 12rem;
}

.cron-interval-control .input {
  max-width: 6rem;
}

.cron-toggle {
  align-items: center;
  display: inline-flex;
  gap: 0.4rem;
  white-space: nowrap;
}

.cron-history h2,
.cron-worker-panel h2 {
  font-size: 1.1rem;
  font-weight: 700;
  margin: 0;
}

@media (max-width: 900px) {
  .cron-toolbar,
  .cron-section-heading {
    align-items: stretch;
    flex-direction: column;
  }

  .cron-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .cron-stats {
    grid-template-columns: 1fr;
  }
}
</style>
