<!-- #region ScheduleAtAGlance [C:3] [TYPE Component] [SEMANTICS health,schedule,timeline,validation] -->
<!-- @BRIEF Compact weekly schedule widget showing active validation tasks with day grid, time windows, and cross-referenced health status. -->
<!-- @RELATION CALLS -> [EXT:method:ValidationApi.fetchTasks]] -->
<!-- @RELATION DEPENDS_ON -> [HealthCenterPage] -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:i18n] -->
<!-- @UX_STATE Loading -> Skeleton grid 7x3 with pulse animation -->
<!-- @UX_STATE NoPolicies -> Dashed border placeholder with CTA: "No active schedules. [Create]" -->
<!-- @UX_STATE Ready -> Week grid (left) + policy list (right) + next-check summary -->
<!-- @UX_STATE Error -> Compact error banner with retry -->
<!-- @UX_REACTIVITY Props -> healthItems, selectedEnvId. LocalState -> tasks, loading, error -->
<!-- @UX_FEEDBACK Hover on policy row highlights corresponding day columns -->
<!-- @UX_FEEDBACK Click on policy row navigates to validation task edit page -->
<!-- @UX_TEST Loading -> {wait: skeleton grid, expected: 7 pulse divs} -->
<!-- @UX_TEST Error -> {mock: fetchTasks throws, expected: error banner with retry} -->
<!-- @UX_TEST Empty -> {mock: fetchTasks returns [], expected: placeholder text} -->
<!-- @UX_TEST Ready -> {mock: fetchTasks returns 1 task, expected: grid + policy list + nextUpcoming} -->
<script lang="ts">
  import { onMount } from 'svelte';
  import { SvelteSet } from 'svelte/reactivity';
  import { goto } from '$app/navigation';
  import { getValidationTasks as fetchTasks } from '$lib/api.js';
  import { t } from '$lib/i18n/index.svelte.js';

  /** @type {{ healthItems: Array<{task_id?: string, status: string}>, selectedEnvId?: string, appTimezone?: string }} */
  let {
    healthItems = [],
    selectedEnvId = '',
    appTimezone = 'Europe/Moscow'
  } = $props();

  // ── State ──────────────────────────────────────────────────────
  /** @type {Array<any>} */
  let tasks = $state([]);
  let loading = $state(true);
  let error = $state(null);
  let hoveredTaskId = $state(null);

  // ── Constants ──────────────────────────────────────────────────
  // ISO: 0=Mon ... 6=Sun (matches existing validation page convention)
  const DAY_SHORT = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];

  // ── Derived ────────────────────────────────────────────────────

  /** Only tasks with an active schedule, optionally filtered by environment */
  let scheduledTasks = $derived.by(() => {
    let result = tasks.filter(t =>
      t.is_active !== false
      && Array.isArray(t.schedule_days)
      && t.schedule_days.length > 0
      && t.window_start
      && t.window_end
    );
    if (selectedEnvId) {
      result = result.filter(t => t.environment_id === selectedEnvId);
    }
    return result;
  });

  /** Build a status map: task_id -> best status from health items */
  let taskStatusMap = $derived.by(() => {
    /** @type {Record<string, string>} */
    const map = {};
    for (const item of healthItems) {
      if (!item.task_id) continue;
      const curr = map[item.task_id];
      // Priority: FAIL > WARN > UNKNOWN > PASS
      if (!curr || item.status === 'FAIL' || (item.status === 'WARN' && curr === 'PASS') || (item.status === 'UNKNOWN' && (curr === 'PASS' || !curr))) {
        map[item.task_id] = item.status;
      }
    }
    return map;
  });

  /** Aggregate status for a given task across all its dashboards in health data */
  function getTaskStatus(taskId) {
    return taskStatusMap[taskId] || null;
  }

  /** Total unique dashboards across all scheduled tasks */
  let totalDashboards = $derived.by(() => {
    const ids = new SvelteSet();
    for (const t of scheduledTasks) {
      if (Array.isArray(t.dashboard_ids)) {
        for (const id of t.dashboard_ids) ids.add(id);
      }
    }
    return ids.size;
  });

  /** Find the next upcoming check day+time */
  let nextUpcoming = $derived.by(() => {
    const now = new Date();
    const todayIdx = (now.getDay() + 6) % 7; // Convert JS Sunday=0 to ISO Monday=0
    /** @type {{task: any, dayIdx: number, dayDate: Date, timeStr: string}|null} */
    let best = null;

    for (const t of scheduledTasks) {
      if (!Array.isArray(t.schedule_days)) continue;
      for (const dayIdx of t.schedule_days) {
        // dayIdx is ISO: 0=Mon
        let diff = dayIdx - todayIdx;
        if (diff < 0 || (diff === 0 && (!t.window_start || t.window_start <= now.toTimeString().slice(0, 5)))) {
          diff += 7;
        }
        // eslint-disable-next-line svelte/prefer-svelte-reactivity
        const candidateDate = new Date(now);
        candidateDate.setDate(candidateDate.getDate() + diff);

        if (!best || candidateDate < best.dayDate) {
          best = { task: t, dayIdx, dayDate: candidateDate, timeStr: t.window_start };
        }
      }
    }
    return best;
  });

  /** Check if ANY scheduled task runs on a given day */
  function isDayActive(dayIdx) {
    for (const t of scheduledTasks) {
      if (Array.isArray(t.schedule_days) && t.schedule_days.includes(dayIdx)) return true;
    }
    return false;
  }

  /** Get all tasks that run on a given day */
  function getTasksForDay(dayIdx) {
    return scheduledTasks.filter(t => Array.isArray(t.schedule_days) && t.schedule_days.includes(dayIdx));
  }

  /** Check if a specific task runs on a given day */
  function taskRunsOnDay(task, dayIdx) {
    return Array.isArray(task.schedule_days) && task.schedule_days.includes(dayIdx);
  }

  // ── Lifecycle ──────────────────────────────────────────────────

  async function loadScheduledTasks() {
    loading = true;
    error = null;
    try {
      const response = await fetchTasks({ is_active: true, page_size: 100 });
      tasks = response?.items || response?.tasks || [];
    } catch (e) {
      error = e?.message || 'Failed to load scheduled tasks';
    } finally {
      loading = false;
    }
  }

  onMount(() => {
    loadScheduledTasks();
  });

  // ── Helpers ────────────────────────────────────────────────────

  const STATUS_COLORS = {
    PASS: { bg: 'bg-green-100', text: 'text-green-700', dot: 'bg-green-500', label: 'pass' },
    WARN: { bg: 'bg-yellow-100', text: 'text-yellow-700', dot: 'bg-yellow-500', label: 'warn' },
    FAIL: { bg: 'bg-red-100', text: 'text-red-700', dot: 'bg-red-500', label: 'fail' },
    UNKNOWN: { bg: 'bg-gray-100', text: 'text-gray-600', dot: 'bg-gray-400', label: 'unknown' },
  };

  function getStatusStyle(status) {
    return STATUS_COLORS[status] || STATUS_COLORS.UNKNOWN;
  }

  function getTaskColor(task) {
    const status = getTaskStatus(task.id);
    if (status) return getStatusStyle(status);
    const lastStatus = task.last_run_status;
    if (lastStatus) return getStatusStyle(lastStatus);
    return null;
  }

  function formatTimeRange(start, end) {
    if (!start && !end) return '';
    if (start && end) return `${start.slice(0, 5)}–${end.slice(0, 5)}`;
    return start ? `${start.slice(0, 5)}` : `–${end.slice(0, 5)}`;
  }

  function formatNextDay(dayIdx) {
    // dayIdx is ISO: 0=Mon
    if (typeof dayIdx !== 'number') return '';
    const key = `days_${DAY_SHORT[dayIdx]}`;
    const localized = $t.health?.[key];
    if (localized && localized !== key) return localized;
    // Neutral English fallback
    return DAY_SHORT[dayIdx].charAt(0).toUpperCase() + DAY_SHORT[dayIdx].slice(1, 3);
  }

  function handleTaskClick(taskId) {
    if (!taskId) return;
    // eslint-disable-next-line svelte/no-navigation-without-resolve
    goto(`/validation/${encodeURIComponent(taskId)}`);
  }
</script>

<div
  class="bg-white rounded-lg border border-gray-200 overflow-hidden"
  role="region"
  aria-label={$t.health?.schedule_title || 'Validation Schedule'}
>
  <!-- Header -->
  <div class="px-5 py-3 border-b border-gray-100 bg-gray-50/50 flex items-center justify-between">
    <div class="flex items-center gap-2">
      <svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
      </svg>
      <h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wider">
        {$t.health?.schedule_title || 'Schedule'}
      </h3>
    </div>

    {#if !loading && !error && scheduledTasks.length > 0}
      <div class="flex items-center gap-3 text-xs text-gray-500">
        <span class="hidden sm:inline">
          {$t.health?.schedule_active_count?.replace('{count}', String(scheduledTasks.length)) || `${scheduledTasks.length} active`}
        </span>
        <span class="hidden sm:inline">·</span>
        <span>
          {$t.health?.schedule_dashboard_count?.replace('{count}', String(totalDashboards)) || `${totalDashboards} dashboards`}
        </span>
      </div>
    {/if}
  </div>

  <!-- Loading -->
  {#if loading}
    <div class="p-5 animate-pulse">
      <div class="flex gap-2 mb-4">
        {#each Array(7) as _, i (i)}
          <div class="flex-1 h-16 bg-gray-100 rounded-lg"></div>
        {/each}
      </div>
      <div class="space-y-2">
        <div class="h-4 bg-gray-100 rounded w-3/4"></div>
        <div class="h-4 bg-gray-100 rounded w-1/2"></div>
      </div>
    </div>

  <!-- Error -->
  {:else if error}
    <div class="p-5">
      <div class="flex items-center gap-2 text-sm text-red-600">
        <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
        <span>{error}</span>
        <button
          onclick={loadScheduledTasks}
          class="ml-auto text-blue-600 hover:text-blue-700 underline text-sm"
        >
          {$t.common?.retry || 'Retry'}
        </button>
      </div>
    </div>

  <!-- No Schedules -->
  {:else if scheduledTasks.length === 0}
    <div class="p-8 text-center">
      <div class="inline-flex items-center justify-center w-10 h-10 rounded-full bg-gray-100 mb-3">
        <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
        </svg>
      </div>
      <p class="text-sm text-gray-500 mb-1">
        {$t.health?.schedule_empty || 'No active validation schedules.'}
      </p>
      <p class="text-xs text-gray-400">
        {$t.health?.schedule_empty_desc || 'Create a validation task with a schedule to see it here.'}
      </p>
    </div>

  <!-- Ready State -->
  {:else}
    <div class="p-5">
      <div class="flex flex-col lg:flex-row gap-5">
        <!-- LEFT: 7-day Grid -->
        <div class="flex-shrink-0">
          <div class="grid grid-cols-7 gap-1.5">
            {#each DAY_SHORT as dayLabel, idx (idx)}
              {@const active = isDayActive(idx)}
              {@const dayTasks = getTasksForDay(idx)}
              <div
                class="relative flex flex-col items-center justify-center w-10 h-14 sm:w-14 sm:h-20 rounded-lg border text-center transition-all duration-150
                  {active
                    ? 'bg-blue-50 border-blue-200 shadow-sm'
                    : 'bg-gray-50/50 border-gray-100 text-gray-400'}
                  {hoveredTaskId ? (dayTasks.some(t => t.id === hoveredTaskId) ? 'ring-2 ring-blue-300' : 'opacity-60') : ''}"
                title={active ? `${dayTasks.length} task(s) on ${DAY_SHORT[idx]}` : ''}
              >
                <span class="text-[10px] font-semibold uppercase tracking-wide {active ? 'text-blue-600' : 'text-gray-400'}">
                  {$t.validation?.[`days_${dayLabel}`] || dayLabel.toUpperCase()}
                </span>
                {#if active}
                  {#each dayTasks.slice(0, 2) as task (task.id)}
                    {@const tc = getTaskColor(task)}
                    <span
                      class="block w-3 h-0.5 rounded-full mt-0.5 {tc?.dot || 'bg-blue-300'}"
                      title={task.name}
                    ></span>
                  {/each}
                  {#if dayTasks.length > 2}
                    <span class="text-[9px] text-blue-400 font-medium mt-0.5">+{dayTasks.length - 2}</span>
                  {/if}
                {/if}
              </div>
            {/each}
          </div>

          <!-- Time scale hint -->
          <div class="mt-2 flex items-center gap-1 text-[10px] text-gray-400 justify-center">
            <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
            {#if scheduledTasks.length === 1}
              <span>{formatTimeRange(scheduledTasks[0].window_start, scheduledTasks[0].window_end)}</span>
            {:else}
              <span>{$t.health?.schedule_multi_time || 'Multiple windows'}</span>
            {/if}
            <span class="text-gray-300">·</span>
            <span>{appTimezone}</span>
          </div>
        </div>

        <!-- RIGHT: Policy List -->
        <div class="flex-1 min-w-0">
          <div class="space-y-2">
            {#each scheduledTasks as task (task.id)}
              {@const status = getTaskStatus(task.id)}
              {@const tc = status ? getStatusStyle(status) : null}
              <button
                onclick={() => handleTaskClick(task.id)}
                onmouseenter={() => (hoveredTaskId = task.id)}
                onmouseleave={() => (hoveredTaskId = null)}
                class="w-full text-left flex items-center gap-3 px-3 py-2 rounded-lg border border-transparent hover:border-gray-200 hover:bg-gray-50 transition-all cursor-pointer group"
              >
                <!-- Status dot -->
                <span
                  class="flex-shrink-0 w-2.5 h-2.5 rounded-full {tc?.dot || 'bg-gray-300'}"
                  title={status || task.last_run_status || 'no data'}
                ></span>

                <!-- Task info -->
                <div class="flex-1 min-w-0">
                  <div class="flex items-center gap-2">
                    <span class="text-sm font-medium text-gray-900 truncate">{task.name}</span>
                    {#if task.environment_id}
                      <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-gray-100 text-gray-500 flex-shrink-0">
                        {task.environment_id}
                      </span>
                    {/if}
                  </div>
                  <div class="flex items-center gap-2 text-xs text-gray-500 mt-0.5">
                    <!-- Day badges -->
                    <span class="flex gap-0.5">
                      {#each DAY_SHORT as dayLabel, idx (idx)}
                        {#if taskRunsOnDay(task, idx)}
                          <span class="inline-flex items-center justify-center w-5 h-4 rounded text-[9px] font-medium bg-blue-50 text-blue-600 border border-blue-100">
                            {$t.validation?.[`days_${dayLabel}`] || dayLabel.slice(0, 2).toUpperCase()}
                          </span>
                        {:else}
                          <span class="inline-flex items-center justify-center w-5 h-4 rounded text-[9px] text-gray-300">
                            {dayLabel.slice(0, 1).toUpperCase()}
                          </span>
                        {/if}
                      {/each}
                    </span>
                    <span class="text-gray-300">·</span>
                    <span class="font-medium text-gray-600">{formatTimeRange(task.window_start, task.window_end)}</span>
                    <span class="text-gray-300">·</span>
                    <span>
                      {Array.isArray(task.dashboard_ids) ? task.dashboard_ids.length : 0} {$t.health?.schedule_dashboards || 'dashboards'}
                    </span>
                  </div>
                </div>

                <!-- Chevron -->
                <svg class="w-4 h-4 text-gray-300 group-hover:text-gray-500 transition-colors flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
                </svg>
              </button>
            {/each}
          </div>

          <!-- Next upcoming -->
          {#if nextUpcoming}
            <div class="mt-3 pt-3 border-t border-gray-100">
              <div class="flex items-center gap-2 text-xs text-gray-500">
                <svg class="w-3.5 h-3.5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />
                </svg>
                <span class="font-medium text-gray-700">{$t.health?.schedule_next || 'Next'}:</span>
                <span>{nextUpcoming.task.name}</span>
                <span class="text-gray-300">·</span>
                <span class="font-medium text-blue-600">
                  {formatNextDay(nextUpcoming.dayIdx)} {nextUpcoming.timeStr?.slice(0, 5)} {appTimezone}
                </span>
              </div>
            </div>
          {/if}
        </div>
      </div>
    </div>
  {/if}
</div>
<!-- #endregion ScheduleAtAGlance -->
