<template>
  <div class="worker-schedules-view">
    <div class="admin-header">
      <h1>{{ $t('admin.falukant.workerSchedules.title') }}</h1>
      <p>{{ $t('admin.falukant.workerSchedules.description') }}</p>
    </div>

    <div v-if="!hasAccess" class="access-denied">
      {{ $t('admin.falukant.workerSchedules.accessDenied') }}
    </div>

    <template v-else>
      <div class="toolbar">
        <label class="toggle">
          <input v-model="detailed" type="checkbox" />
          <span>{{ $t('admin.falukant.workerSchedules.detailed') }}</span>
        </label>
        <label class="toggle">
          <input v-model="autoRefresh" type="checkbox" />
          <span>{{ $t('admin.falukant.workerSchedules.autoRefresh') }}</span>
        </label>
        <button type="button" @click="requestSchedules">
          {{ $t('admin.falukant.workerSchedules.refresh') }}
        </button>
      </div>

      <p v-if="generatedAt" class="generated-at">
        {{ $t('admin.falukant.workerSchedules.generatedAt') }}: {{ formatTs(generatedAt) }}
      </p>

      <div v-if="error" class="error">{{ error }}</div>
      <div v-else-if="loading" class="loading">{{ $t('general.loading') }}</div>
      <p v-else-if="!schedules.length" class="empty">{{ $t('admin.falukant.workerSchedules.empty') }}</p>

      <div v-else class="workers">
        <article v-for="worker in schedules" :key="worker.worker" class="worker-card">
          <header class="worker-card__header">
            <h2>{{ worker.worker }}</h2>
            <div v-if="detailed" class="runtime">
              <span :class="['badge', worker.running_worker ? 'ok' : 'off']">
                worker: {{ worker.running_worker ? 'on' : 'off' }}
              </span>
              <span :class="['badge', worker.running_watchdog ? 'ok' : 'off']">
                watchdog: {{ worker.running_watchdog ? 'on' : 'off' }}
              </span>
            </div>
          </header>
          <p v-if="detailed && worker.current_step" class="step">
            {{ $t('admin.falukant.workerSchedules.currentStep') }}: {{ worker.current_step }}
            <span v-if="worker.last_step_change_ts">
              ({{ formatTs(worker.last_step_change_ts) }})
            </span>
          </p>

          <table v-if="Array.isArray(worker.tasks) && worker.tasks.length" class="tasks">
            <thead>
              <tr>
                <th>{{ $t('admin.falukant.workerSchedules.task') }}</th>
                <th>{{ $t('admin.falukant.workerSchedules.cadence') }}</th>
                <th>{{ $t('admin.falukant.workerSchedules.nextRun') }}</th>
                <th>{{ $t('admin.falukant.workerSchedules.remaining') }}</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="task in worker.tasks" :key="`${worker.worker}-${task.task}`">
                <td>{{ task.task }}</td>
                <td>{{ task.cadence_label || `${task.cadence_seconds}s` }}</td>
                <td>{{ task.next_run_latest_ts ? formatTs(task.next_run_latest_ts) : '—' }}</td>
                <td>{{ formatRemaining(task.next_run_latest_in_seconds) }}</td>
              </tr>
            </tbody>
          </table>
          <p v-else class="empty-tasks">{{ $t('admin.falukant.workerSchedules.noTasks') }}</p>
        </article>
      </div>
    </template>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'AdminFalukantWorkerSchedulesView',
  data() {
    return {
      detailed: false,
      autoRefresh: true,
      schedules: [],
      generatedAt: null,
      loading: false,
      error: null,
      timer: null,
    };
  },
  computed: {
    ...mapState(['daemonSocket', 'daemonConnectionStatus', 'menu', 'user']),
    hasAccess() {
      const path = '/admin/falukant/worker-schedules';
      const walk = (node) => {
        if (!node || typeof node !== 'object') return false;
        if (Array.isArray(node)) return node.some(walk);
        if (node.path === path) return true;
        if (node.children) return walk(node.children);
        return Object.values(node).some(walk);
      };
      return walk(this.menu);
    },
    daemonConnected() {
      return this.daemonSocket && this.daemonConnectionStatus === 'connected' && this.daemonSocket.readyState === WebSocket.OPEN;
    },
  },
  watch: {
    autoRefresh: {
      immediate: true,
      handler() {
        this.setupTimer();
      },
    },
    detailed() {
      this.requestSchedules();
    },
    daemonSocket(newSocket, oldSocket) {
      if (oldSocket) oldSocket.removeEventListener('message', this.handleDaemonMessage);
      if (newSocket) newSocket.addEventListener('message', this.handleDaemonMessage);
    },
    daemonConnectionStatus(status) {
      if (status === 'connected') this.requestSchedules();
    },
  },
  mounted() {
    if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
    this.requestSchedules();
  },
  beforeUnmount() {
    if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
    if (this.timer) clearInterval(this.timer);
  },
  methods: {
    getDaemonUserId() {
      return this.user?.hashedId || this.user?.id || this.user?.username || '';
    },
    sendSetUserId() {
      const userId = this.getDaemonUserId();
      if (!userId) return false;
      this.daemonSocket.send(JSON.stringify({
        event: 'setUserId',
        data: { userId },
      }));
      return true;
    },
    setupTimer() {
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
      if (!this.autoRefresh) return;
      this.timer = setInterval(() => this.requestSchedules(), 15000);
    },
    requestSchedules() {
      if (!this.hasAccess) return;
      if (!this.daemonConnected) {
        this.error = this.$t('admin.falukant.workerSchedules.notConnected');
        return;
      }
      if (!this.sendSetUserId()) {
        this.error = this.$t('admin.falukant.workerSchedules.missingUserId');
        return;
      }
      this.loading = true;
      this.error = null;
      const event = this.detailed ? 'getWorkerSchedulesDetailed' : 'getWorkerSchedules';
      try {
        this.daemonSocket.send(JSON.stringify({ event, data: {} }));
      } catch (err) {
        this.loading = false;
        this.error = this.$t('admin.falukant.workerSchedules.sendError');
      }
    },
    handleDaemonMessage(evt) {
      try {
        const payload = JSON.parse(evt.data);
        if (payload.event !== 'getWorkerSchedulesResponse' && payload.event !== 'getWorkerSchedulesDetailedResponse') return;
        this.loading = false;
        if (payload.ok === false) {
          this.error = payload.error || this.$t('admin.falukant.workerSchedules.responseError');
          return;
        }
        this.generatedAt = payload.generated_at || null;
        this.schedules = Array.isArray(payload.schedules) ? payload.schedules : [];
        this.error = null;
      } catch (_) {
        // ignore unrelated messages
      }
    },
    formatTs(unixTs) {
      if (!unixTs) return '—';
      return new Date(Number(unixTs) * 1000).toLocaleString();
    },
    formatRemaining(seconds) {
      if (seconds == null) return '—';
      const s = Number(seconds);
      if (!Number.isFinite(s)) return '—';
      if (s < 60) return `${s}s`;
      if (s < 3600) return `${Math.floor(s / 60)}m ${s % 60}s`;
      return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
    },
  },
};
</script>

<style scoped>
.worker-schedules-view { padding: 16px; }
.toolbar { display: flex; gap: 12px; align-items: center; margin: 12px 0; flex-wrap: wrap; }
.toggle { display: inline-flex; gap: 6px; align-items: center; }
.generated-at { color: #666; margin-bottom: 8px; }
.workers { display: grid; gap: 12px; }
.worker-card { border: 1px solid #ddd; border-radius: 8px; padding: 12px; background: #fff; }
.worker-card__header { display: flex; justify-content: space-between; gap: 12px; align-items: center; flex-wrap: wrap; }
.runtime { display: inline-flex; gap: 8px; }
.badge { font-size: .75rem; padding: 3px 8px; border-radius: 999px; }
.badge.ok { background: #e7f7ea; color: #216b2f; }
.badge.off { background: #f8eceb; color: #8d2f2a; }
.tasks { width: 100%; border-collapse: collapse; margin-top: 8px; }
.tasks th, .tasks td { border: 1px solid #e5e5e5; padding: 6px 8px; text-align: left; }
.empty, .empty-tasks { color: #666; }
.error { color: #a1261c; background: #ffecea; border: 1px solid #f0b6b0; padding: 8px; border-radius: 6px; }
.access-denied { color: #a1261c; font-weight: 600; }
</style>
