<!-- #region DashboardHub [C:4] [TYPE Page] [SEMANTICS sveltekit, dashboard, hub, git, bulk-action] -->
<!-- @BRIEF Dashboard hub page for environment-scoped dashboard management with Git status, validation badges, selection, and bulk migrate/backup actions. -->
<!-- @LAYER Page -->
<!-- @RELATION DEPENDS_ON -> [MappingTable] -->
<!-- @RELATION DEPENDS_ON -> [StartupEnvironmentWizard] -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:environmentContextStore] -->
<!-- @RELATION CALLS -> [EXT:frontend:api_module] -->
<!-- @RELATION CALLS -> [EXT:frontend:gitService] -->
<!-- @UX_STATE Loading -> Skeleton loader shown. -->
<!-- @UX_STATE Loaded -> Dashboard grid with status badges rendered. -->
<!-- @UX_STATE Error -> Error banner with retry button. -->
<!-- @UX_STATE Selecting -> Checkboxes checked, floating action panel appears. -->
<!-- @UX_STATE BulkAction-Modal -> Migration or Backup modal open. -->
<!-- @UX_FEEDBACK Floating panel slides up from bottom when items selected. -->
<!-- @UX_RECOVERY Refresh button reloads dashboard list. -->
<script lang="ts">
  import { onMount } from "svelte";
  import { goto } from "$app/navigation";
  import { t } from "$lib/i18n/index.svelte.js";
  import { log } from "$lib/cot-logger";
  import { openDrawerForTask, openDrawerForTaskIfPreferred } from "$lib/stores/taskDrawer.svelte.js";
  import { api } from "$lib/api.js";
  import { debounce } from "$lib/utils/debounce.js";
  import { addToast } from "$lib/toasts.svelte.js";
  import { gitService } from "../../services/gitService.js";
  import MappingTable from "../../components/MappingTable.svelte";
  import StartupEnvironmentWizard from "../../components/StartupEnvironmentWizard.svelte";
  import SearchableMultiSelect from "$lib/components/ui/SearchableMultiSelect.svelte";
  import DashboardMaintenanceBadge from "$lib/components/DashboardMaintenanceBadge.svelte";
  import { MigrationModel } from "../../lib/models/MigrationModel.svelte.ts";
  import { environmentContextStore, initializeEnvironmentContext, setSelectedEnvironment } from "$lib/stores/environmentContext.svelte.js";
  import { Button } from "$lib/ui";
  import ColumnFilterPopover from "./ColumnFilterPopover.svelte";
  import { formatDate, getPaginationRange, getSortIndicator } from "./dashboard-helpers.js";
  import { DashboardHubModel } from "$lib/models/DashboardHubModel.svelte.ts";

  const m = new DashboardHubModel();
  const debouncedSearch = debounce((query: string) => { m.searchQuery = query.trim(); m.currentPage = 1; void m.loadDashboards(); }, 300);

  onMount(async () => {
    await initializeEnvironmentContext();
    if (!m.selectedEnv && $environmentContextStore?.selectedEnvId) m.selectedEnv = $environmentContextStore.selectedEnvId;
  });

  function handleDocumentClick(event: MouseEvent) {
    if (!(event.target as HTMLElement).closest('.action-dropdown')) m.closeActionDropdown();
    if (!(event.target as HTMLElement).closest('.column-filter')) m.openFilterColumn = null;
  }
  if (typeof document !== 'undefined') document.addEventListener('click', handleDocumentClick);

  function handleSearch(event: Event) { debouncedSearch((event.target as HTMLInputElement).value); }
  
  async function handleEnvironmentCreated(envId: string) {
    if (!envId) return;
    m.error = null; m.lastLoadedEnvId = null; m.selectedEnv = envId;
    setSelectedEnvironment(envId);
  }

  // Store-derived values (must stay in page — legacy store not reactive in Model)
  let environments = $derived($environmentContextStore?.environments || []);
  let shouldShowEnvironmentWizard = $derived(
    Boolean($environmentContextStore?.isLoaded) && !$environmentContextStore?.isLoading &&
      !$environmentContextStore?.error && environments.length === 0
  );
  let shouldShowEnvironmentZeroState = $derived(shouldShowEnvironmentWizard && !m.isLoading && !m.error);

  // Environment context sync
  $effect(() => { const ctxEnvId = $environmentContextStore?.selectedEnvId; if (ctxEnvId && m.selectedEnv !== ctxEnvId) m.selectedEnv = ctxEnvId; });
  $effect(() => {
    const ctx = $environmentContextStore;
    if (!ctx || ctx.isLoading) return;
    if (ctx.error && !m.selectedEnv) { m.error = ctx.error; m.isLoading = false; return; }
    if (ctx.isLoaded && ctx.environments.length === 0) {
      m.isLoading = false; m.error = null;
      m.allDashboards = []; m.dashboards = []; m.filteredDashboards = [];
      m.total = 0; m.totalPages = 1; m.serverTotal = 0; m.serverTotalPages = 1; m.lastLoadedEnvId = null;
    }
  });
  $effect(() => { m.filterDashboardIds; queueMicrotask(() => { if (m.allDashboards.length > 0) m.applyGridTransforms(); }); });
  $effect(() => {
    const envId = m.selectedEnv;
    if (!envId || envId === m.lastLoadedEnvId) return;
    m.lastLoadedEnvId = envId; m.currentPage = 1;
    m.profileFilterOverrideShowAll = false; m.effectiveProfileFilter = null;
    m.clearSelectedIds(); m.filterDashboardIds = [];
    void m.loadDashboards(); void m.loadDashboardSearchOptions('');
  });
</script>
<div class="mx-auto w-full px-4 lg:px-8 space-y-6">
  <!-- Header -->
  <div class="flex items-center justify-between">
    <h1 class="text-2xl font-bold text-text">
      {$t.nav?.dashboards}
    </h1>
    <div class="flex items-center space-x-4">
      <button
        class="inline-flex items-center justify-center rounded-lg border border-primary-ring bg-primary-light px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary-light"
        onclick={() => {
          const ids = Array.from(m.selectedIds);
          const query = ids.length > 0 ? `?ids=${ids.join(",")}` : "";
          goto(`/validation-tasks/new${query}`);
        }}
      >
        + {$t.dashboard?.create_validation_task || "Create Validation Task"}
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
        onclick={() => m.loadDashboards()}
      >
        {$t.common?.refresh}
      </button>
    </div>
  </div>

  {#if m.effectiveProfileFilter?.applied}
    <div class="rounded-lg border border-primary-ring bg-primary-light px-4 py-2 text-sm text-primary">
      <div class="flex flex-wrap items-center justify-between gap-2">
        <span class="font-medium">
          {$t.profile?.filter_badge_active || "Profile filters active"}
        </span>
        <button
          type="button"
          class="rounded border border-primary bg-surface-card px-2.5 py-1 text-xs font-medium text-primary hover:bg-primary-light"
          onclick={() => m.handleTemporaryShowAll()}
        >
          {$t.profile?.filter_show_all_temporarily || "Show all dashboards temporarily"}
        </button>
      </div>
    </div>
  {:else if m.effectiveProfileFilter?.override_show_all}
    <div class="rounded-lg border border-warning bg-warning-light px-4 py-2 text-sm text-warning">
      <div class="flex flex-wrap items-center justify-between gap-2">
        <span class="font-medium">
          {$t.profile?.filter_badge_override || "Showing all dashboards temporarily"}
        </span>
        <button
          type="button"
          class="rounded border border-warning bg-surface-card px-2.5 py-1 text-xs font-medium text-warning hover:bg-warning-light"
          onclick={() => m.handleRestoreProfileFilter()}
        >
          {$t.profile?.filter_restore_default || "Restore default filter"}
        </button>
      </div>
    </div>
  {/if}

  <!-- Error Banner -->
  {#if m.error}
    <div
      class="bg-destructive-light border border-destructive-ring text-destructive px-4 py-3 rounded mb-4 flex items-center justify-between"
    >
      <span>{m.error}</span>
      <Button variant="destructive" onclick={() => m.loadDashboards()}>{$t.common?.retry}</Button>
    </div>
  {/if}

  <!-- Loading State -->
  {#if m.isLoading}
    <div
      class="bg-surface-card border border-border rounded-xl shadow-sm overflow-hidden"
    >
      <div
        class="grid gap-4 px-6 py-3 bg-surface-muted border-b border-border font-semibold text-[11px] uppercase tracking-wide text-text-muted"
        style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
      >
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
      </div>
      {#each Array(5) as _}
        <div
          class="grid gap-4 px-6 py-4 border-b border-border last:border-b-0 hover:bg-surface-muted transition-colors"
          style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
        >
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
          <div class="h-4 rounded bg-surface-muted animate-pulse"></div>
        </div>
      {/each}
    </div>
  {:else if shouldShowEnvironmentZeroState}
    <div class="relative overflow-hidden rounded-3xl border border-border bg-surface-page p-8 shadow-sm">
      <div class="max-w-2xl">
        <p class="text-xs font-semibold uppercase tracking-[0.24em] text-primary">
          {$t.dashboard?.setup_badge || "Initial setup"}
        </p>
        <h2 class="mt-3 text-2xl font-semibold text-text">
          {$t.dashboard?.setup_empty_title || "No environments configured yet"}
        </h2>
        <p class="mt-3 text-sm leading-6 text-text-muted">
          {$t.dashboard?.setup_empty_body ||
            "Add the first Superset environment to unlock dashboards, datasets, backups, and migrations."}
        </p>
        <div class="mt-6 flex flex-wrap gap-3">
          <button
            type="button"
            class="rounded-xl bg-primary px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-primary-hover"
          >
            {$t.dashboard?.setup_start || "Start setup"}
          </button>
          <button
            type="button"
            class="rounded-xl border border-border-strong px-4 py-2.5 text-sm font-medium text-text transition hover:border-border-strong hover:text-text"
            onclick={() => goto("/settings?tab=environments#environments")}
          >
            {$t.dashboard?.setup_open_advanced || "Advanced settings"}
          </button>
        </div>
      </div>
    </div>
  {:else if m.dashboards.length === 0}
    <!-- Empty State -->
    <div class="py-12 text-center text-text-muted">
      <svg
        class="w-16 h-16 mx-auto mb-4 text-text-subtle"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
      >
        <path d="M3 3h18v18H3V3zm16 16V5H5v14h14z" />
      </svg>

      {#if m.effectiveProfileFilter?.applied}
        <p>{$t.profile?.filter_empty_state || "No dashboards found for active profile filters. Try adjusting your filter settings."}</p>
        <div class="mt-4">
          <button
            type="button"
            class="rounded border border-primary bg-surface-card px-3 py-1.5 text-xs font-medium text-primary hover:bg-primary-light"
            onclick={() => m.handleTemporaryShowAll()}
          >
            {$t.profile?.filter_show_all_temporarily || "Show all dashboards temporarily"}
          </button>
        </div>
      {:else}
        <p>{$t.dashboard?.empty}</p>
      {/if}
    </div>
  {:else}
    <!-- Toolbar -->
    <div class="flex items-center justify-between mb-4 gap-4">
      <div class="flex items-center gap-2">
        <Button variant="secondary" size="sm" onclick={() => m.handleSelectAll()} disabled={m.total === 0}>
          {m.isAllSelected ? $t.dashboard?.deselect_all : $t.dashboard?.select_all}
        </Button>
        <Button variant="secondary" size="sm" onclick={() => m.handleSelectVisible()} disabled={m.dashboards.length === 0}>
          {m.isAllVisibleSelected ? $t.dashboard?.deselect_visible : $t.dashboard?.select_visible}
        </Button>
        {#if m.selectedIds.size > 0}
          <span class="text-sm text-text-muted">
            {($t.dashboard?.selected_count).replace(
              "{count}",
              String(m.selectedIds.size),
            )}
          </span>
        {/if}
      </div>
      <div class="w-80">
        <SearchableMultiSelect
          options={m.searchableDashboardOptions}
          bind:selected={m.filterDashboardIds}
          placeholder={$t.dashboard?.search}
          selectAllLabel={$t.dashboard?.select_all}
          clearLabel={$t.common?.clear || "Clear"}
          emptyMessage="No dashboards match"
          loading={m.searchableDashboardLoading}
          onSearch={m.loadDashboardSearchOptions}
          debounceMs={300}
        />
      </div>
    </div>

    <!-- Dashboard Grid -->
    <div class="bg-surface-card border border-border rounded-xl shadow-sm">
      <div class="overflow-x-auto overflow-y-visible">
        <!-- Grid Header -->
        <div
          class="grid gap-4 px-6 py-3 bg-surface-muted border-b border-border font-semibold text-[11px] uppercase tracking-wide text-text-muted"
          style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
        >
          <div></div>
          <div class="flex items-center gap-2">
            <button
              class="hover:text-text transition-colors"
              onclick={() => m.handleSort("title")}
            >
              {$t.dashboard?.title}
              {getSortIndicator(m.sortColumn, "title", m.sortDirection)}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter(
                  'title',
                )
                  ? 'border-primary-ring text-primary bg-primary-light'
                  : 'border-border-strong text-text-muted hover:text-text'}"
                onclick={(event) => m.toggleFilterDropdown("title", event, 256)}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if m.openFilterColumn === "title"}
                <ColumnFilterPopover
                  column="title"
                  bind:filterValues={columnFilters.title}
                  bind:searchText={m.columnFilterSearch.title}
                  options={m.getVisibleFilterOptions("title")}
                  position={m.filterDropdownPosition}
                  onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("title", v, c)}
                  onClear={() => m.clearColumnFilter("title")}
                />
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button
              class="hover:text-text transition-colors"
              onclick={() => m.handleSort("git_status")}
            >
              GIT {getSortIndicator(m.sortColumn, "git_status", m.sortDirection)}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter(
                  'git_status',
                )
                  ? 'border-primary-ring text-primary bg-primary-light'
                  : 'border-border-strong text-text-muted hover:text-text'}"
                onclick={(event) =>
                  m.toggleFilterDropdown("git_status", event, 256)}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if m.openFilterColumn === "git_status"}
                <ColumnFilterPopover
                  column="git_status"
                  bind:filterValues={columnFilters.git_status}
                  bind:searchText={m.columnFilterSearch.git_status}
                  options={m.getVisibleFilterOptions("git_status")}
                  position={m.filterDropdownPosition}
                  onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("git_status", v, c)}
                  onClear={() => m.clearColumnFilter("git_status")}
                />
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button
              class="hover:text-text transition-colors"
              onclick={() => m.handleSort("validation_status")}
            >
              {$t.dashboard?.validation || "Validation"}
              {getSortIndicator(m.sortColumn, "validation_status", m.sortDirection)}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter(
                  'validation_status',
                )
                  ? 'border-primary-ring text-primary bg-primary-light'
                  : 'border-border-strong text-text-muted hover:text-text'}"
                onclick={(event) =>
                  m.toggleFilterDropdown("validation_status", event, 200)}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if m.openFilterColumn === "validation_status"}
                <ColumnFilterPopover
                  column="validation_status"
                  bind:filterValues={columnFilters.validation_status}
                  bind:searchText={m.columnFilterSearch.validation_status}
                  options={[]}
                  staticOptions={["pass", "warn", "fail", "unknown"]}
                  position={m.filterDropdownPosition}
                  widthPx={200}
                  showSearch={false}
                  onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("validation_status", v, c)}
                />
              {/if}
            </div>
          </div>
          <div class="flex items-center text-xs text-text-muted uppercase">
            {$t.maintenance?.maintenance_badge || "Maint."}
          </div>
          <div class="flex items-center gap-2">
            {$t.dashboard?.actions}
          </div>
          <div class="flex items-center gap-2">
            <button
              class="hover:text-text transition-colors"
              onclick={() => m.handleSort("changed_on")}
            >
              {$t.dashboard?.changed_on}
              {getSortIndicator(m.sortColumn, "changed_on", m.sortDirection)}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter(
                  'changed_on',
                )
                  ? 'border-primary-ring text-primary bg-primary-light'
                  : 'border-border-strong text-text-muted hover:text-text'}"
                onclick={(event) =>
                  m.toggleFilterDropdown("changed_on", event, 256)}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if m.openFilterColumn === "changed_on"}
                <ColumnFilterPopover
                  column="changed_on"
                  bind:filterValues={columnFilters.changed_on}
                  bind:searchText={m.columnFilterSearch.changed_on}
                  options={m.getVisibleFilterOptions("changed_on")}
                  position={m.filterDropdownPosition}
                  onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("changed_on", v, c)}
                  onClear={() => m.clearColumnFilter("changed_on")}
                />
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button
              class="hover:text-text transition-colors"
              onclick={() => m.handleSort("actor")}
            >
              {$t.dashboard?.owners || "Owners"}
              {getSortIndicator(m.sortColumn, "actor", m.sortDirection)}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {m.hasColumnFilter(
                  'actor',
                )
                  ? 'border-primary-ring text-primary bg-primary-light'
                  : 'border-border-strong text-text-muted hover:text-text'}"
                onclick={(event) => m.toggleFilterDropdown("actor", event, 288)}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if m.openFilterColumn === "actor"}
                <ColumnFilterPopover
                  column="actor"
                  bind:filterValues={columnFilters.actor}
                  bind:searchText={m.columnFilterSearch.actor}
                  options={m.getVisibleFilterOptions("actor")}
                  position={m.filterDropdownPosition}
                  widthPx={288}
                  onToggleFilter={(v: string, c: boolean) => m.toggleFilterValue("actor", v, c)}
                  onClear={() => m.clearColumnFilter("actor")}
                />
              {/if}
            </div>
          </div>
        </div>

        <!-- Grid Rows -->
        {#each m.dashboards as dashboard}
          <div
            class="grid items-center gap-4 px-6 py-3 border-b border-border last:border-b-0 hover:bg-surface-muted transition-colors text-[13px]"
            style="width: max(100%, 1460px); grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(70px,0.5fr) minmax(110px,0.7fr) minmax(280px,2fr) minmax(150px,1fr) 124px;"
          >
            <!-- Checkbox -->
            <div>
              <input
                type="checkbox"
                checked={m.selectedIds.has(dashboard.id)}
                onchange={(e) => m.handleCheckboxChange(dashboard, e)}
              />
            </div>

            <!-- Title -->
            <div class="font-medium text-sm text-text">
              <button
                class="text-left text-primary hover:text-primary-hover hover:underline transition-colors"
                onclick={() => m.navigateToDashboardDetail(dashboard)}
                title={$t.dashboard?.open_overview}
              >
                {dashboard.title}
              </button>
            </div>

            <!-- Git Status -->
            <div>
              <div class="flex flex-col gap-1">
                <span
                  class="inline-flex w-fit items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide {dashboard.git?.hasRepo === null
                    ? 'bg-surface-muted text-text-muted'
                    : dashboard.git?.hasRepo
                      ? 'bg-primary-light text-primary'
                      : 'bg-surface-muted text-text'}"
                >
                  {dashboard.git?.hasRepo === null
                    ? $t.common?.loading || "Loading"
                    : dashboard.git?.hasRepo
                    ? $t.dashboard?.status_repo || "Repo"
                    : $t.dashboard?.status_no_repo || "No Repo"}
                </span>
                {#if dashboard.git?.hasRepo}
                  <span
                    class="inline-flex w-fit items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide {dashboard
                      .git?.hasChangesForCommit
                      ? 'bg-warning-light text-warning'
                      : 'bg-success-light text-success'}"
                  >
                    {dashboard.git?.hasChangesForCommit
                      ? $t.dashboard?.status_changes || "Diff"
                      : $t.dashboard?.status_no_changes || "Synced"}
                  </span>
                {/if}
              </div>
            </div>

            <!-- Validation Status Indicator -->
            <div class="relative">
              {#if m.validationStatuses[dashboard.id]}
                {@const vs = m.validationStatuses[dashboard.id]}
                {@const dotClass = vs.status === "PASS" ? "bg-success" : vs.status === "WARN" ? "bg-warning" : vs.status === "FAIL" ? "bg-destructive" : "bg-border"}
                <button
                  class="inline-flex h-5 w-5 items-center justify-center rounded-full border border-border-strong transition-colors hover:ring-2 hover:ring-border-strong"
                  onclick={(e) => m.toggleValidationPopover(dashboard.id, e)}
                  title={vs.status || "UNKNOWN"}
                  aria-label={$t.dashboard?.validation_status || "Validation status"}
                >
                  <span class="block h-3 w-3 rounded-full {dotClass}"></span>
                </button>
              {:else if m.validationStatusLoading}
                <span class="inline-flex h-5 w-5 items-center justify-center">
                  <span class="block h-3 w-3 animate-pulse rounded-full bg-surface-muted"></span>
                </span>
              {:else}
                <span class="text-text-subtle text-xs">-</span>
              {/if}

              <!-- Validation Popover -->
              {#if m.openValidationPopover === dashboard.id}
                <div
                  class="fixed z-50 w-80 rounded-lg border border-border bg-surface-card shadow-lg"
                  style="left: {m.validationPopoverPosition.left}px; top: {m.validationPopoverPosition.top}px;"
                  onclick={(e) => e.stopPropagation()}
                >
                  <div class="border-b border-border px-3 py-2 text-xs font-semibold uppercase tracking-wide text-text-muted">
                    {$t.dashboard?.validation_history || "Validation History"}
                  </div>
                  {#if m.validationStatuses[dashboard.id]?.history?.length > 0}
                    <div class="max-h-48 overflow-y-auto">
                      {#each m.validationStatuses[dashboard.id].history.slice(0, 3) as run}
                        <div class="flex items-center justify-between px-3 py-2 text-xs border-b border-border last:border-b-0">
                          <div class="flex items-center gap-2">
                            <span class="block h-2.5 w-2.5 rounded-full {run.status === 'PASS' ? 'bg-success' : run.status === 'WARN' ? 'bg-warning' : run.status === 'FAIL' ? 'bg-destructive' : 'bg-border'}"></span>
                            <span class="text-text">{run.status || "UNKNOWN"}</span>
                          </div>
                          <div class="flex items-center gap-2">
                            <span class="text-text-subtle">{run.task_name || "-"}</span>
                            <span class="text-text-subtle">{formatDate(run.last_run_at)}</span>
                          </div>
                        </div>
                      {/each}
                    </div>
                  {:else}
                    <div class="px-3 py-3 text-xs text-text-subtle text-center">
                      {$t.dashboard?.no_validation_runs || "No validation runs yet"}
                    </div>
                  {/if}
                  <div class="border-t border-border px-3 py-2">
                    <a
                      href={`/validation-tasks?search=${dashboard.id}`}
                      class="text-xs text-primary hover:text-primary-hover hover:underline"
                    >
                      {$t.dashboard?.view_all_runs || "View all runs →"}
                    </a>
                  </div>
                </div>
              {/if}
            </div>

            <!-- Maintenance Badge -->
            <div>
              <DashboardMaintenanceBadge dashboardId={dashboard.id} />
            </div>

            <!-- Actions -->
            <div class="flex items-center">
              <div class="flex items-center gap-1.5">
                <button
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-border bg-surface-card text-primary hover:bg-primary-light hover:border-primary-ring transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
          onclick={() => m.handleAction(dashboard, "migrate")}
                  title={$t.dashboard?.action_migrate}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M5 12h14M12 5l7 7-7 7" />
                  </svg>
                </button>
                <button
                  class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-border bg-surface-card text-warning hover:bg-warning-light hover:border-warning transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  onclick={() => m.handleAction(dashboard, "backup")}
                  title={$t.dashboard?.action_backup}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                    <polyline points="7,10 12,15 17,10" />
                    <line x1="12" y1="15" x2="12" y2="3" />
                  </svg>
                </button>
              </div>
            </div>

            <!-- Changed On -->
            <div class="text-xs text-text-muted">
              {dashboard.changedOnLabel}
            </div>

            <!-- Owners -->
            <div class="text-xs">
              {#if dashboard.owners?.length > 0}
                <div class="flex flex-wrap items-center gap-1">
                  {#each dashboard.owners as owner (owner)}
                    <span
                      class="rounded bg-surface-muted px-2 py-0.5 text-xs text-text truncate max-w-[180px]"
                      title={owner}
                    >
                      {owner}
                    </span>
                  {/each}
                </div>
              {:else}
                <div class="text-text-subtle">-</div>
              {/if}
            </div>
          </div>
        {/each}
      </div>

      <!-- Pagination -->
      {#if m.totalPages > 1}
        <div
          class="flex items-center justify-between px-4 py-3 bg-surface-muted border-t border-border"
        >
          <div class="text-sm text-text-muted">
            {($t.dashboard?.showing)
              .replace("{start}", String((m.currentPage - 1) * m.pageSize + 1))
              .replace("{end}", String(Math.min(m.currentPage * m.pageSize, m.total)))
              .replace("{total}", String(m.total))}
          </div>
          <div class="flex items-center gap-1">
            <button
              class="px-3 py-1 border border-border-strong rounded hover:bg-surface-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
              onclick={() => m.setPage(1)}
              disabled={m.currentPage === 1}
            >
              {$t.common?.first}
            </button>
            <button
              class="px-3 py-1 border border-border-strong rounded hover:bg-surface-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
              onclick={() => m.setPage(m.currentPage - 1)}
              disabled={m.currentPage === 1}
            >
              {$t.dashboard?.previous}
            </button>
            {#each getPaginationRange(m.currentPage, m.totalPages) as pageNum}
              {#if pageNum === "..."}
                <span class="px-2 py-1 text-text-muted">...</span>
              {:else}
                <button
                  class="page-btn {pageNum === m.currentPage ? 'active' : ''}"
                  onclick={() => m.setPage(pageNum)}
                >
                  {pageNum}
                </button>
              {/if}
            {/each}
            <button
              class="px-3 py-1 border border-border-strong rounded hover:bg-surface-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
              onclick={() => m.setPage(m.currentPage + 1)}
              disabled={m.currentPage === m.totalPages}
            >
              {$t.dashboard?.next}
            </button>
            <button
              class="px-3 py-1 border border-border-strong rounded hover:bg-surface-muted disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
              onclick={() => m.setPage(m.totalPages)}
              disabled={m.currentPage === m.totalPages}
            >
              {$t.common?.last}
            </button>
          </div>
          <div>
            <select
class="px-4 py-2 border border-border-strong rounded-lg bg-surface-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-ring"
              value={m.pageSize}
              onchange={(e) => m.setPageSize(e)}
            >
              <option value={5}>
                {($t.dashboard?.per_page_option).replace("{count}", "5")}
              </option>
              <option value={10}>
                {($t.dashboard?.per_page_option).replace("{count}", "10")}
              </option>
              <option value={25}>
                {($t.dashboard?.per_page_option).replace("{count}", "25")}
              </option>
              <option value={50}>
                {($t.dashboard?.per_page_option).replace("{count}", "50")}
              </option>
              <option value={100}>
                {($t.dashboard?.per_page_option).replace("{count}", "100")}
              </option>
            </select>
          </div>
        </div>
      {/if}
    </div>

    <!-- Floating Bulk Action Panel -->
    {#if m.selectedIds.size > 0}
      <div
        class="fixed bottom-0 left-0 right-0 bg-surface-card border-t border-border shadow-lg p-4 transition-transform transform translate-y-0"
      >
        <div
          class="flex items-center justify-between w-full px-4 lg:px-8 mx-auto"
        >
          <div class="flex items-center gap-4">
            <span class="font-medium">
              ✓ {($t.dashboard?.selected_count).replace(
                "{count}",
                String(m.selectedIds.size),
              )}
            </span>
          </div>
          <div class="flex gap-3">
            <button
              class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors"
              onclick={() => {
                m.showMigrateModal = true;
                m.isEditingMappings = false;
                m.bulkMigrateModel.sourceEnvId = m.selectedEnv;
                m.bulkMigrateModel.selectedDashboardIds = Array.from(m.selectedIds);
                m.bulkMigrateModel.targetEnvId = "";
                m.bulkMigrateModel.dryRunResult = null;
                m.bulkMigrateModel.error = "";
                m.bulkMigrateModel.sourceDatabases = [];
                m.bulkMigrateModel.targetDatabases = [];
                m.bulkMigrateModel.mappings = [];
                m.bulkMigrateModel.suggestions = [];
                m.bulkMigrateModel.currentStep = 1;
              }}
            >
              {$t.dashboard?.bulk_migrate}
            </button>
            <button
              class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors"
              onclick={() => (m.showBackupModal = true)}
            >
              {$t.dashboard?.bulk_backup}
            </button>
            <button
              class="px-3 py-1 text-sm border border-border-strong rounded hover:bg-surface-muted transition-colors"
              onclick={() => m.clearSelectedIds()}
            >
              {$t.common?.cancel}
            </button>
          </div>
        </div>
      </div>
    {/if}
  {/if}

  <!-- Migration Modal -->
  {#if m.showMigrateModal}
    <div
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
      onclick={() => (m.showMigrateModal = false)}
      onkeydown={(e) => e.key === "Escape" && (m.showMigrateModal = false)}
      role="presentation"
    >
      <div
        class="bg-surface-card rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
        onclick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        tabindex="-1"
        aria-labelledby="migrate-modal-title"
        onkeydown={(e) => e.stopPropagation()}
      >
        <div
          class="px-6 py-4 border-b border-border flex items-center justify-between relative"
        >
          <h2 id="migrate-modal-title" class="text-xl font-bold">
            {($t.dashboard?.migrate_modal_title).replace(
              "{count}",
              String(m.selectedIds.size),
            )}
          </h2>
          <button
            onclick={() => (m.showMigrateModal = false)}
            class="absolute top-4 right-4 p-2 text-text-subtle hover:text-text-muted hover:bg-surface-muted rounded-full transition-all"
            aria-label={$t.common?.close_modal}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          </button>
        </div>
        <div class="px-6 py-4">
          <div class="space-y-6">
            <!-- Source Env (Read-only) -->
            <div>
              <label
                for="source-env"
                class="block text-sm font-medium text-text-muted mb-1"
                >{$t.migration?.source_env}</label
              >
              <div
                id="source-env"
                class="px-4 py-2 bg-surface-muted border border-border rounded-lg text-text font-medium"
              >
                {environments.find((e) => e.id === m.selectedEnv)?.name ||
                  m.selectedEnv}
                {$t.dashboard?.read_only}
              </div>
            </div>

            <!-- Target Env -->
            <div>
              <label
                for="target-env"
                class="block text-sm font-medium text-text mb-2"
                >{$t.migration?.target_env}</label
              >
              <select
                id="target-env"
                class="env-dropdown w-full"
                value={m.bulkMigrateModel.targetEnvId}
                onchange={(e) => {
                  m.bulkMigrateModel.selectTargetEnv(e.target.value);
                  if (e.target.value) {
                    m.bulkMigrateModel.fetchDatabases().then(() => {
                      if (m.bulkMigrateModel.selectedDashboardIds.length > 0 && m.bulkMigrateModel.dryRunResult === null) {
                        m.bulkMigrateModel.calculateDryRun();
                      }
                    });
                  }
                }}
              >
                <option value="">
                  {$t.dashboard?.target_env_placeholder ||
                    "Select target environment..."}
                </option>
                {#each environments.filter((e) => e.id !== m.selectedEnv) as env}
                  <option value={env.id}>{env.name}</option>
                {/each}
              </select>
            </div>

            <!-- Database Mappings Toggle -->
            <div>
              <div class="flex items-center justify-between mb-2">
                <label
                  for="use-db-mappings"
                  class="block text-sm font-medium text-text"
                  >{$t.migration?.database_mappings}</label
                >
                <label class="relative inline-flex items-center cursor-pointer">
                  <input
                    type="checkbox"
                    id="use-db-mappings"
                    bind:checked={m.bulkMigrateModel.replaceDb}
                    class="sr-only peer"
                  />
                  <div
                    class="w-9 h-5 bg-surface-muted peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary-ring rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-surface-card after:border-border-strong after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary"
                  ></div>
                  <span class="ml-2 text-xs text-text-muted"
                    >{m.bulkMigrateModel.replaceDb ? $t.common?.on : $t.common?.off}</span
                  >
                </label>
              </div>

              {#if m.bulkMigrateModel.replaceDb}
                <div class="flex items-center justify-between mb-2">
                  {#if m.bulkMigrateModel.suggestions.length > 0}
                    <button
                      class="text-sm text-primary hover:underline"
                      onclick={() => (m.isEditingMappings = !m.isEditingMappings)}
                    >
                      {m.isEditingMappings
                        ? $t.dashboard?.view_summary
                        : $t.dashboard?.edit_mappings}
                    </button>
                  {/if}
                </div>

                {#if m.isEditingMappings}
                  <div
                    class="border border-border rounded-lg overflow-hidden"
                  >
                    <MappingTable
                      sourceDatabases={m.bulkMigrateModel.sourceDatabases}
                      targetDatabases={m.bulkMigrateModel.targetDatabases}
                      mappings={m.bulkMigrateModel.mappings}
                      suggestions={m.bulkMigrateModel.suggestions}
                      onupdate={(e) => m.bulkMigrateModel.saveMapping(e.detail.sourceUuid, e.detail.targetUuid)}
                    />
                  </div>
                {:else}
                  <div
                    class="border border-border rounded-lg overflow-hidden"
                  >
                    <table class="w-full text-sm text-left">
                      <thead class="bg-surface-muted border-b border-border">
                        <tr>
                          <th class="px-4 py-2 font-semibold text-text"
                            >{$t.dashboard?.source_database}</th
                          >
                          <th class="px-4 py-2 font-semibold text-text"
                            >{$t.dashboard?.target_database}</th
                          >
                          <th class="px-4 py-2 font-semibold text-text"
                            >{$t.dashboard?.match_percent}</th
                          >
                        </tr>
                      </thead>
                      <tbody>
                        {#if m.bulkMigrateModel.suggestions.length > 0}
                          {#each m.bulkMigrateModel.suggestions as mapping}
                            {@const targetMapping = m.bulkMigrateModel.mappings.find(
                              (m) => m.source_db_uuid === mapping.source_db_uuid,
                            )}
                            <tr
                              class="border-b border-border last:border-b-0"
                            >
                              <td class="px-4 py-2">{mapping.source_db || mapping.source_db_name}</td>
                              <td class="px-4 py-2">
                                {#if targetMapping}
                                  {m.bulkMigrateModel.targetDatabases.find(
                                    (d) =>
                                      d.uuid === targetMapping.target_db_uuid,
                                  )?.database_name || mapping.target_db || mapping.target_db_name}
                                {:else}
                                  <span class="text-destructive"
                                    >{$t.dashboard?.not_mapped}</span
                                  >
                                {/if}
                              </td>
                              <td class="px-4 py-2">
                                <span
                                  class="px-2 py-0.5 rounded-full text-xs font-medium {mapping.confidence >
                                  0.9
                                    ? 'bg-success-light text-success'
                                    : 'bg-warning-light text-warning'}"
                                >
                                  {Math.round(mapping.confidence * 100)}%
                                </span>
                              </td>
                            </tr>
                          {/each}
                        {:else}
                          <tr>
                            <td
                              colspan="3"
                              class="px-4 py-4 text-center text-text-muted italic"
                            >
                              {m.bulkMigrateModel.targetEnvId
                                ? $t.dashboard?.no_databases_to_map ||
                                  "No databases found to map"
                                : $t.dashboard?.select_target_for_mappings ||
                                  "Select target environment to see mappings"}
                            </td>
                          </tr>
                        {/if}
                      </tbody>
                    </table>
                  </div>
                {/if}
              {:else}
                <p class="text-xs text-text-subtle italic">
                  {$t.dashboard?.mapping_disabled_hint ||
                    "Database mapping is disabled. Dashboards will be imported with original database references."}
                </p>
              {/if}
            </div>

            <!-- Fix Cross-Filters (Spec 022 FR-007) -->
            <div class="bg-primary-light border border-primary-ring rounded-lg p-4">
              <label class="flex items-start gap-3 cursor-pointer">
                <input
                  type="checkbox"
                  bind:checked={m.bulkMigrateModel.fixCrossFilters}
                  class="mt-0.5 h-4 w-4 rounded border-border-strong text-primary focus-visible:ring-primary-ring"
                />
                <div>
                  <span class="text-sm font-medium text-text"
                    >{$t.dashboard?.fix_cross_filters_title ||
                      "Fix cross-filter bindings"}</span
                  >
                  <p class="text-xs text-text-muted mt-0.5">
                    {$t.dashboard?.fix_cross_filters_hint ||
                      "Automatically remap chart and dataset IDs in cross-filters to target environment IDs. Recommended when migrating dashboards with cross-filters."}
                  </p>
                </div>
              </label>
            </div>

            <!-- Selected Dashboards List -->
            <div>
              <p class="block text-sm font-medium text-text mb-2">
                {$t.dashboard?.selected_dashboards}
              </p>
              <div
                class="max-h-40 overflow-y-auto border border-border rounded-lg bg-surface-muted p-2"
              >
                {#each Array.from(m.selectedIds) as id}
                  {#each m.allDashboards as d}
                    {#if d.id === id}
                      <div class="flex items-center text-sm py-1 px-2">
                        <span class="text-success mr-2">☑</span>
                        <span class="text-text">{d.title}</span>
                      </div>
                    {/if}
                  {/each}
                {/each}
              </div>
            </div>

            <!-- Dry Run Summary -->
            {#if m.bulkMigrateModel.targetEnvId && m.selectedIds.size > 0}
              <div class="bg-primary-light border border-primary-ring rounded-lg p-4">
                {#if m.bulkMigrateModel.dryRunLoading}
                  <div class="flex items-center gap-2 text-primary text-sm">
                    <svg
                      class="animate-spin h-4 w-4"
                      xmlns="http://www.w3.org/2000/svg"
                      fill="none"
                      viewBox="0 0 24 24"
                    >
                      <circle
                        class="opacity-25"
                        cx="12"
                        cy="12"
                        r="10"
                        stroke="currentColor"
                        stroke-width="4"
                      ></circle>
                      <path
                        class="opacity-75"
                        fill="currentColor"
                        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                      ></path>
                    </svg>
                    <span
                      >{$t.migration?.calculating_dry_run ||
                        "Calculating changes..."}</span
                    >
                  </div>
                {:else if m.bulkMigrateModel.error && !m.bulkMigrateModel.dryRunResult}
                  <div class="text-destructive text-sm">
                    <span class="font-medium">{$t.common?.error}:</span>
                    {m.bulkMigrateModel.error}
                  </div>
                {:else if m.bulkMigrateModel.dryRunResult?.summary}
                  {@const totalDashboards =
                    m.bulkMigrateModel.dryRunResult.summary.dashboards?.create +
                      m.bulkMigrateModel.dryRunResult.summary.dashboards?.update +
                      m.bulkMigrateModel.dryRunResult.summary.dashboards?.delete || 0}
                  {@const totalCharts =
                    m.bulkMigrateModel.dryRunResult.summary.charts?.create +
                      m.bulkMigrateModel.dryRunResult.summary.charts?.update +
                      m.bulkMigrateModel.dryRunResult.summary.charts?.delete || 0}
                  {@const totalDatasets =
                    m.bulkMigrateModel.dryRunResult.summary.datasets?.create +
                      m.bulkMigrateModel.dryRunResult.summary.datasets?.update +
                      m.bulkMigrateModel.dryRunResult.summary.datasets?.delete || 0}
                  {@const totalAll =
                    totalDashboards + totalCharts + totalDatasets}
                  <div
                    class="text-sm font-medium text-primary-hover flex items-start gap-2"
                  >
                    <svg
                      class="w-5 h-5 text-primary mt-0.5 shrink-0"
                      xmlns="http://www.w3.org/2000/svg"
                      viewBox="0 0 20 20"
                      fill="currentColor"
                    >
                      <path
                        fill-rule="evenodd"
                        d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                        clip-rule="evenodd"
                      />
                    </svg>
                    <span>
                      {(
                        $t.migration?.dry_run_summary ||
                        "Dry-run report: {total} items will be changed (Dashboards: {dashboards}, Charts: {charts}, Datasets: {datasets})"
                      )
                        .replace("{total}", totalAll)
                        .replace("{dashboards}", totalDashboards)
                        .replace("{charts}", totalCharts)
                        .replace("{datasets}", totalDatasets)}
                    </span>
                  </div>
                {/if}
              </div>
            {/if}
          </div>
        </div>
        <div class="px-6 py-4 border-t border-border flex justify-end gap-3">
          <button
            class="px-3 py-1 text-sm border border-border-strong rounded hover:bg-surface-muted transition-colors"
            onclick={() => (m.showMigrateModal = false)}
            disabled={m.bulkMigrateModel.dryRunLoading || m.bulkMigrateModel.currentStep >= 4}>{$t.common?.cancel}</button
          >
          <button
            class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            onclick={async () => {
              if (!m.bulkMigrateModel.canExecute) return;
              m.bulkMigrateModel.goToStep(4);
              await m.bulkMigrateModel.executeMigration("/dashboards/migrate");
              if (!m.bulkMigrateModel.error) {
                m.showMigrateModal = false;
                m.clearSelectedIds();
                m.bulkMigrateModel.deselectAllDashboards();
                const task = m.bulkMigrateModel.selectedTaskStore.current;
                if (task?.id) openDrawerForTaskIfPreferred(task.id);
              }
            }}
            disabled={!m.bulkMigrateModel.canExecute || m.bulkMigrateModel.dryRunLoading || m.bulkMigrateModel.currentStep >= 4}
          >
            {m.bulkMigrateModel.currentStep >= 4 ? $t.dashboard?.starting : $t.migration?.start}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Backup Modal -->
  {#if m.showBackupModal}
    <div
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
      onclick={() => (m.showBackupModal = false)}
      onkeydown={(e) => e.key === "Escape" && (m.showBackupModal = false)}
      role="presentation"
    >
      <div
        class="bg-surface-card rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
        onclick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        tabindex="-1"
        aria-labelledby="backup-modal-title"
        onkeydown={(e) => e.stopPropagation()}
      >
        <div
          class="px-6 py-4 border-b border-border flex items-center justify-between relative"
        >
          <h2 id="backup-modal-title" class="text-xl font-bold">
            {($t.dashboard?.backup_modal_title).replace(
              "{count}",
              String(m.selectedIds.size),
            )}
          </h2>
          <button
            onclick={() => (m.showBackupModal = false)}
            class="absolute top-4 right-4 p-2 text-text-subtle hover:text-text-muted hover:bg-surface-muted rounded-full transition-all"
            aria-label={$t.common?.close_modal}
          >
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          </button>
        </div>
        <div class="px-6 py-4">
          <div class="space-y-6">
            <!-- Env (Read-only) -->
            <div>
              <label
                for="backup-env"
                class="block text-sm font-medium text-text-muted mb-1"
                >{$t.dashboard?.environment}</label
              >
              <div
                id="backup-env"
                class="px-4 py-2 bg-surface-muted border border-border rounded-lg text-text font-medium"
              >
                {environments.find((e) => e.id === m.selectedEnv)?.name ||
                  m.selectedEnv}
                {$t.dashboard?.read_only}
              </div>
            </div>

            <!-- Selected Dashboards List -->
            <div>
              <p class="block text-sm font-medium text-text mb-2">
                {$t.dashboard?.selected_dashboards}
              </p>
              <div
                class="max-h-40 overflow-y-auto border border-border rounded-lg bg-surface-muted p-2"
              >
                {#each Array.from(m.selectedIds) as id}
                  {#each m.allDashboards as d}
                    {#if d.id === id}
                      <div class="flex items-center text-sm py-1 px-2">
                        <span class="text-success mr-2">☑</span>
                        <span class="text-text">{d.title}</span>
                      </div>
                    {/if}
                  {/each}
                {/each}
              </div>
            </div>

            <!-- Schedule -->
            <div>
              <p
                id="backup-schedule-label"
                class="block text-sm font-medium text-text mb-2"
              >
                {$t.dashboard?.schedule}
              </p>
              <div class="space-y-3" aria-labelledby="backup-schedule-label">
                <label class="flex items-center">
                  <input
                    type="radio"
                    name="schedule"
                    value=""
                    bind:group={m.backupSchedule}
                    class="mr-2"
                  />
                  <span class="text-sm">{$t.dashboard?.one_time_backup}</span>
                </label>
                <label class="flex items-center">
                  <input
                    type="radio"
                    name="schedule"
                    value="0 0 * * *"
                    bind:group={m.backupSchedule}
                    class="mr-2"
                  />
                  <span class="text-sm">{$t.dashboard?.schedule_backup}</span>
                </label>
                {#if m.backupSchedule !== ""}
                  <div class="ml-6">
                    <label
                      for="cron-expression"
                      class="block text-xs text-text-muted mb-1"
                      >{$t.dashboard?.cron_expression}</label
                    >
                    <input
                      id="cron-expression"
                      type="text"
                      class="search-input w-full text-sm"
                      placeholder={$t.dashboard?.cron_placeholder ||
                        "0 2 * * * (daily at 2 AM)"}
                      bind:value={m.backupSchedule}
                    />
                    <button class="text-xs text-primary hover:underline mt-1"
                      >{$t.dashboard?.cron_help}</button
                    >
                  </div>
                {/if}
              </div>
            </div>
          </div>
        </div>
        <div class="px-6 py-4 border-t border-border flex justify-end gap-3">
          <button
            class="px-3 py-1 text-sm border border-border-strong rounded hover:bg-surface-muted transition-colors"
            onclick={() => (m.showBackupModal = false)}
            disabled={m.isSubmittingBackup}>{$t.common?.cancel}</button
          >
          <button
            class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
            onclick={() => m.handleBulkBackup()}
            disabled={m.selectedIds.size === 0 || m.isSubmittingBackup}
          >
            {m.isSubmittingBackup
              ? $t.dashboard?.starting
              : $t.dashboard?.start_backup}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <StartupEnvironmentWizard
    open={shouldShowEnvironmentWizard}
    onCreated={handleEnvironmentCreated}
  />
</div>

<!-- #endregion DashboardHub -->
