<!-- [DEF:DashboardHub:Page] -->
<script>
  /**
   * @TIER: CRITICAL
   * @PURPOSE: Dashboard Hub - Central hub for managing dashboards with Git status and task actions
   * @LAYER: UI
   * @RELATION: BINDS_TO -> sidebarStore, taskDrawerStore
   * @INVARIANT: Always shows dashboards for the active environment from context store
   *
   * @UX_STATE: Loading -> Shows skeleton loader
   * @UX_STATE: Loaded -> Shows dashboard grid with status badges
   * @UX_STATE: Error -> Shows 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
   *
   * @TEST_CONTRACT Page_DashboardHub ->
   * {
   *   required_props: {},
   *   optional_props: {},
   *   invariants: [
   *       "Loads specific dashboards for currently selected environment store context",
   *       "Reflects Git sync status explicitly via row badges",
   *       "Submits migration action carrying db_mappings safely"
   *   ]
   * }
   * @TEST_FIXTURE init_state -> {}
   * @TEST_EDGE server_error -> shows error block and retry button
   * @TEST_EDGE bulk_git_actions -> verifies modal functionality when triggered
   * @TEST_INVARIANT rendering_and_selection -> verifies: [init_state]
   */

  import { onMount } from "svelte";
  import { goto } from "$app/navigation";
  import { t } from "$lib/i18n";
  import { openDrawerForTask } from "$lib/stores/taskDrawer.js";
  import { api } from "$lib/api.js";
  import { debounce } from "$lib/utils/debounce.js";
  import { addToast } from "$lib/toasts.js";
  import { gitService } from "../../services/gitService.js";
  import MappingTable from "$components/MappingTable.svelte";
  import {
    environmentContextStore,
    initializeEnvironmentContext,
  } from "$lib/stores/environmentContext.js";

  // State
  let selectedEnv = null;
  let allDashboards = [];
  let dashboards = [];
  let filteredDashboards = [];
  let isLoading = true;
  let error = null;

  // Pagination state
  let currentPage = 1;
  let pageSize = 10;
  let totalPages = 1;
  let total = 0;

  // Selection state
  let selectedIds = new Set();
  let isAllSelected = false;
  let isAllVisibleSelected = false;

  // Search state
  let searchQuery = "";
  let sortColumn = "title";
  let sortDirection = "asc";
  let openFilterColumn = null;
  let columnFilterSearch = {
    title: "",
    git_status: "",
    llm_status: "",
    changed_on: "",
    actor: "",
  };
  let columnFilters = {
    title: new Set(),
    git_status: new Set(),
    llm_status: new Set(),
    changed_on: new Set(),
    actor: new Set(),
  };

  // Bulk action modal state
  let showMigrateModal = false;
  let showBackupModal = false;
  let targetEnvId = null;
  let backupSchedule = "";
  let isSubmittingMigrate = false;
  let isSubmittingBackup = false;
  let dbMappings = {};
  let availableDbMappings = [];
  let sourceDatabases = [];
  let targetDatabases = [];
  let isEditingMappings = false;
  let useDbMappings = true;
  let fixCrossFilters = true;

  // Individual action dropdown state
  let openActionDropdown = null; // stores dashboard ID

  // Validation state
  let validatingIds = new Set();
  let gitBusyIds = new Set();
  let cachedGitConfigs = [];

  // Environment options - will be loaded from API
  let environments = [];

  // Debounced search function
  const debouncedSearch = debounce((query) => {
    searchQuery = query;
    currentPage = 1;
    applyGridTransforms();
  }, 300);

  // Load environments and dashboards on mount
  onMount(async () => {
    await initializeEnvironmentContext();
    if (!selectedEnv && $environmentContextStore?.selectedEnvId) {
      selectedEnv = $environmentContextStore.selectedEnvId;
    }
    await loadDashboards();
  });

  // Handle document click to close dropdowns
  function handleDocumentClick(event) {
    if (!event.target.closest(".action-dropdown")) {
      closeActionDropdown();
    }
    if (!event.target.closest(".column-filter")) {
      openFilterColumn = null;
    }
  }

  // Add document click listener
  if (typeof document !== "undefined") {
    document.addEventListener("click", handleDocumentClick);
  }

  // Load dashboards from API
  // [DEF:DashboardHub.normalizeTaskStatus:Function]
  /**
   * @PURPOSE: Normalize raw task status to stable lowercase token for UI.
   * @PRE: status can be enum-like string or null.
   * @POST: returns null or normalized token without enum namespace.
   */
  function normalizeTaskStatus(status) {
    if (!status) return null;
    const raw = String(status).trim();
    const plain = raw.includes(".") ? raw.split(".").pop() : raw;
    return plain ? plain.toLowerCase() : null;
  }

  // [/DEF:DashboardHub.normalizeTaskStatus:Function]

  // [DEF:DashboardHub.normalizeValidationStatus:Function]
  /**
   * @PURPOSE: Normalize validation status to pass/fail/warn/unknown.
   * @PRE: status can be any scalar.
   * @POST: returns one of pass|fail|warn|unknown.
   */
  function normalizeValidationStatus(status) {
    if (!status) return "unknown";
    const normalized = String(status).trim().toUpperCase();
    if (normalized === "PASS") return "pass";
    if (normalized === "FAIL") return "fail";
    if (normalized === "WARN") return "warn";
    return "unknown";
  }

  // [/DEF:DashboardHub.normalizeValidationStatus:Function]

  // [DEF:DashboardHub.getValidationBadgeClass:Function]
  /**
   * @PURPOSE: Map validation level to badge class tuple.
   * @PRE: level in pass|fail|warn|unknown.
   * @POST: returns deterministic tailwind class string.
   */
  function getValidationBadgeClass(level) {
    if (level === "pass") return "bg-emerald-100 text-emerald-700 border-emerald-200";
    if (level === "fail") return "bg-rose-100 text-rose-700 border-rose-200";
    if (level === "warn") return "bg-amber-100 text-amber-700 border-amber-200";
    return "bg-slate-100 text-slate-700 border-slate-200";
  }

  // [/DEF:DashboardHub.getValidationBadgeClass:Function]

  // [DEF:DashboardHub.getValidationLabel:Function]
  /**
   * @PURPOSE: Map normalized validation level to compact UI label.
   * @PRE: level in pass|fail|warn|unknown.
   * @POST: returns uppercase status label.
   */
  function getValidationLabel(level) {
    if (level === "pass") return "PASS";
    if (level === "fail") return "FAIL";
    if (level === "warn") return "WARN";
    return "UNKNOWN";
  }

  // [/DEF:DashboardHub.getValidationLabel:Function]

  // [DEF:DashboardHub.normalizeOwners:Function]
  /**
   * @PURPOSE: Normalize owners payload to unique non-empty display labels.
   * @PRE: owners can be null, list of strings, or list of user objects.
   * @POST: Returns owner labels preserving source order.
   */
  function normalizeOwners(owners) {
    if (!Array.isArray(owners)) return [];

    const result = [];
    for (const owner of owners) {
      let label = null;
      if (typeof owner === "string") {
        const trimmed = owner.trim();
        if (trimmed) label = trimmed;
      } else if (owner && typeof owner === "object") {
        const firstName = typeof owner.first_name === "string" ? owner.first_name.trim() : "";
        const lastName = typeof owner.last_name === "string" ? owner.last_name.trim() : "";
        const fullName = [firstName, lastName].filter(Boolean).join(" ");
        label =
          fullName ||
          (typeof owner.full_name === "string" ? owner.full_name.trim() : "") ||
          (typeof owner.username === "string" ? owner.username.trim() : "") ||
          (typeof owner.email === "string" ? owner.email.trim() : "");
      }

      if (label && !result.includes(label)) {
        result.push(label);
      }
    }

    return result;
  }

  // [/DEF:DashboardHub.normalizeOwners:Function]

  // [DEF:DashboardHub.loadDashboards:Function]
  /**
   * @PURPOSE: Load full dashboard dataset for current environment and hydrate grid projection.
   * @PRE: selectedEnv is not null.
   * @POST: allDashboards, dashboards, pagination and selection state are synchronized.
   * @UX_STATE: Loading -> true during request lifecycle.
   * @UX_STATE: Error -> `error` populated when request fails.
   */
  async function loadDashboards() {
    if (!selectedEnv) return;

    isLoading = true;
    error = null;
    try {
      const firstResponse = await api.getDashboards(selectedEnv, {
        page: 1,
        page_size: 100,
      });

      const allPages = [firstResponse];
      if (firstResponse.total_pages > 1) {
        const pageRequests = [];
        for (let page = 2; page <= firstResponse.total_pages; page += 1) {
          pageRequests.push(
            api.getDashboards(selectedEnv, {
              page,
              page_size: 100,
            }),
          );
        }
        const nextPages = await Promise.all(pageRequests);
        allPages.push(...nextPages);
      }

      const rawDashboards = allPages.flatMap((response) => response.dashboards);

      allDashboards = rawDashboards.map((d) => {
        const owners = normalizeOwners(d.owners);
        return {
          id: d.id,
          title: d.title,
          slug: d.slug,
          changedOn: d.last_modified || null,
          changedOnLabel: formatDate(d.last_modified),
          owners,
          actorLabel: owners.length > 0 ? owners.join(", ") : "-",
          git: {
            status: d.git_status?.sync_status?.toLowerCase() || "no_repo",
            branch: d.git_status?.branch || null,
            hasRepo: d.git_status?.has_repo === true,
            hasChangesForCommit: d.git_status?.has_changes_for_commit === true,
          },
          lastTask: d.last_task
            ? {
                status: normalizeTaskStatus(d.last_task.status),
                validationStatus: normalizeValidationStatus(
                  d.last_task.validation_status,
                ),
                id: d.last_task.task_id,
              }
            : null,
          actions: ["migrate", "backup"],
        };
      });

      selectedIds = new Set(
        Array.from(selectedIds).filter((id) =>
          allDashboards.some((dashboard) => dashboard.id === id),
        ),
      );
      applyGridTransforms();
      updateSelectionState();
    } catch (err) {
      error = err.message || $t.dashboard?.load_failed;
      console.error("[DashboardHub][Coherence:Failed]", err);
    } finally {
      isLoading = false;
    }
  }

  // Handle search input
  function handleSearch(event) {
    debouncedSearch(event.target.value);
  }

  // Handle page change
  function handlePageChange(page) {
    currentPage = page;
    applyGridTransforms();
  }

  // Handle page size change
  function handlePageSizeChange(event) {
    pageSize = parseInt(event.target.value);
    currentPage = 1;
    applyGridTransforms();
  }

  // Update selection state based on current selection
  function updateSelectionState() {
    const visibleCount = dashboards.length;
    const totalCount = filteredDashboards.length;

    isAllSelected = selectedIds.size === totalCount && totalCount > 0;
    isAllVisibleSelected =
      selectedIds.size === visibleCount && visibleCount > 0;
  }

  // Handle checkbox change for individual dashboard
  function handleCheckboxChange(dashboard, event) {
    if (event.target.checked) {
      selectedIds.add(dashboard.id);
    } else {
      selectedIds.delete(dashboard.id);
    }
    selectedIds = selectedIds; // Trigger reactivity
    updateSelectionState();
  }

  // Handle select all
  function handleSelectAll() {
    if (isAllSelected) {
      selectedIds.clear();
    } else {
      filteredDashboards.forEach((d) => selectedIds.add(d.id));
    }
    selectedIds = selectedIds; // Trigger reactivity
    updateSelectionState();
  }

  // Handle select visible
  function handleSelectVisible() {
    if (isAllVisibleSelected) {
      dashboards.forEach((d) => selectedIds.delete(d.id));
    } else {
      dashboards.forEach((d) => selectedIds.add(d.id));
    }
    selectedIds = selectedIds; // Trigger reactivity
    updateSelectionState();
  }

  // [/DEF:DashboardHub.loadDashboards:Function]

  // [DEF:DashboardHub.formatDate:Function]
  /**
   * @PURPOSE: Convert ISO timestamp to locale date string.
   * @PRE: value may be null or invalid date string.
   * @POST: returns formatted date or "-".
   */
  function formatDate(value) {
    if (!value) return "-";
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return "-";
    return date.toLocaleDateString();
  }

  // [/DEF:DashboardHub.formatDate:Function]

  // [DEF:DashboardHub.getGitSummaryLabel:Function]
  /**
   * @PURPOSE: Compute stable text label for git state column.
   * @PRE: dashboard has git projection fields.
   * @POST: returns localized summary string.
   */
  function getGitSummaryLabel(dashboard) {
    if (!dashboard.git?.hasRepo) {
      return $t.dashboard?.status_no_repo || "No Repo";
    }
    return dashboard.git?.hasChangesForCommit
      ? $t.dashboard?.status_changes || "Diff"
      : $t.dashboard?.status_no_changes || "Synced";
  }

  // [/DEF:DashboardHub.getGitSummaryLabel:Function]

  // [DEF:DashboardHub.getLlmSummaryLabel:Function]
  /**
   * @PURPOSE: Compute normalized LLM validation summary label.
   * @PRE: dashboard may have null lastTask.
   * @POST: returns UNKNOWN fallback for missing status.
   */
  function getLlmSummaryLabel(dashboard) {
    return getValidationLabel(dashboard.lastTask?.validationStatus || "unknown");
  }

  // [/DEF:DashboardHub.getLlmSummaryLabel:Function]

  // [DEF:DashboardHub.getColumnCellValue:Function]
  /**
   * @PURPOSE: Resolve comparable/filterable display value for any grid column.
   * @PRE: column belongs to filterable column set.
   * @POST: returns non-empty scalar display value.
   */
  function getColumnCellValue(dashboard, column) {
    if (column === "title") return dashboard.title || "-";
    if (column === "git_status") return getGitSummaryLabel(dashboard);
    if (column === "llm_status") return getLlmSummaryLabel(dashboard);
    if (column === "changed_on") return dashboard.changedOnLabel || "-";
    if (column === "actor") return dashboard.actorLabel || "-";
    return "-";
  }

  // [/DEF:DashboardHub.getColumnCellValue:Function]

  // [DEF:DashboardHub.getFilterOptions:Function]
  /**
   * @PURPOSE: Build unique sorted value list for a column filter dropdown.
   * @PRE: allDashboards is hydrated.
   * @POST: returns de-duplicated sorted options.
   */
  function getFilterOptions(column) {
    const options = allDashboards
      .map((dashboard) => getColumnCellValue(dashboard, column))
      .filter((value) => Boolean(value));
    return Array.from(new Set(options)).sort((a, b) =>
      a.localeCompare(b, "ru"),
    );
  }

  // [/DEF:DashboardHub.getFilterOptions:Function]

  // [DEF:DashboardHub.getVisibleFilterOptions:Function]
  /**
   * @PURPOSE: Apply in-dropdown search over full filter options.
   * @PRE: columnFilterSearch contains search token for column.
   * @POST: returns subset for current filter popover list.
   */
  function getVisibleFilterOptions(column) {
    const searchText = (columnFilterSearch[column] || "").toLowerCase();
    return getFilterOptions(column).filter((value) =>
      value.toLowerCase().includes(searchText),
    );
  }

  // [/DEF:DashboardHub.getVisibleFilterOptions:Function]

  // [DEF:DashboardHub.toggleFilterDropdown:Function]
  /**
   * @PURPOSE: Toggle active column filter popover.
   * @PRE: column is valid filter key.
   * @POST: openFilterColumn updated.
   */
  function toggleFilterDropdown(column) {
    openFilterColumn = openFilterColumn === column ? null : column;
  }

  // [/DEF:DashboardHub.toggleFilterDropdown:Function]

  // [DEF:DashboardHub.toggleFilterValue:Function]
  /**
   * @PURPOSE: Add/remove specific filter value and reapply projection.
   * @PRE: value comes from option list of the same column.
   * @POST: columnFilters updated and grid reprojected from page 1.
   */
  function toggleFilterValue(column, value, checked) {
    const next = new Set(columnFilters[column]);
    if (checked) {
      next.add(value);
    } else {
      next.delete(value);
    }
    columnFilters = { ...columnFilters, [column]: next };
    currentPage = 1;
    applyGridTransforms();
  }

  // [/DEF:DashboardHub.toggleFilterValue:Function]

  // [DEF:DashboardHub.clearColumnFilter:Function]
  /**
   * @PURPOSE: Reset selected values for one column.
   * @PRE: column is valid filter key.
   * @POST: filter cleared and projection refreshed.
   */
  function clearColumnFilter(column) {
    columnFilters = { ...columnFilters, [column]: new Set() };
    currentPage = 1;
    applyGridTransforms();
  }

  // [/DEF:DashboardHub.clearColumnFilter:Function]

  // [DEF:DashboardHub.selectAllColumnFilterValues:Function]
  /**
   * @PURPOSE: Select all currently visible values in filter popover.
   * @PRE: visible options computed for current search token.
   * @POST: column filter equals current visible option set.
   */
  function selectAllColumnFilterValues(column) {
    columnFilters = {
      ...columnFilters,
      [column]: new Set(getVisibleFilterOptions(column)),
    };
    currentPage = 1;
    applyGridTransforms();
  }

  // [/DEF:DashboardHub.selectAllColumnFilterValues:Function]

  // [DEF:DashboardHub.updateColumnFilterSearch:Function]
  /**
   * @PURPOSE: Update local search token for one filter popover.
   * @PRE: value is text from input.
   * @POST: columnFilterSearch updated immutably.
   */
  function updateColumnFilterSearch(column, value) {
    columnFilterSearch = { ...columnFilterSearch, [column]: value };
  }

  // [/DEF:DashboardHub.updateColumnFilterSearch:Function]

  // [DEF:DashboardHub.hasColumnFilter:Function]
  /**
   * @PURPOSE: Determine if column has active selected values.
   * @PRE: column is valid filter key.
   * @POST: returns boolean activation marker.
   */
  function hasColumnFilter(column) {
    return columnFilters[column]?.size > 0;
  }

  // [/DEF:DashboardHub.hasColumnFilter:Function]

  // [DEF:DashboardHub.doesDashboardPassColumnFilters:Function]
  /**
   * @PURPOSE: Evaluate dashboard row against all active column filters.
   * @PRE: dashboard contains projected values for each filterable column.
   * @POST: returns true only when row matches every active filter.
   */
  function doesDashboardPassColumnFilters(dashboard) {
    const columns = Object.keys(columnFilters);
    for (const column of columns) {
      const values = columnFilters[column];
      if (values.size === 0) continue;
      const dashboardValue = getColumnCellValue(dashboard, column);
      if (!values.has(dashboardValue)) return false;
    }
    return true;
  }

  // [/DEF:DashboardHub.doesDashboardPassColumnFilters:Function]

  // [DEF:DashboardHub.getSortValue:Function]
  /**
   * @PURPOSE: Compute stable comparable sort key for chosen column.
   * @PRE: column belongs to sortable set.
   * @POST: returns string/number key suitable for deterministic comparison.
   */
  function getSortValue(dashboard, column) {
    if (column === "title") return (dashboard.title || "").toLowerCase();
    if (column === "git_status") return (getGitSummaryLabel(dashboard) || "").toLowerCase();
    if (column === "llm_status") return (getLlmSummaryLabel(dashboard) || "").toLowerCase();
    if (column === "changed_on") return dashboard.changedOn ? new Date(dashboard.changedOn).getTime() : 0;
    if (column === "actor") return (dashboard.actorLabel || "").toLowerCase();
    return "";
  }

  // [/DEF:DashboardHub.getSortValue:Function]

  // [DEF:DashboardHub.handleSort:Function]
  /**
   * @PURPOSE: Toggle or switch sort order and reapply grid projection.
   * @PRE: column belongs to sortable set.
   * @POST: sortColumn/sortDirection updated and page reset to 1.
   */
  function handleSort(column) {
    if (sortColumn === column) {
      sortDirection = sortDirection === "asc" ? "desc" : "asc";
    } else {
      sortColumn = column;
      sortDirection = "asc";
    }
    currentPage = 1;
    applyGridTransforms();
  }

  // [/DEF:DashboardHub.handleSort:Function]

  // [DEF:DashboardHub.getSortIndicator:Function]
  /**
   * @PURPOSE: Return visual indicator for active/inactive sort header.
   * @PRE: column belongs to sortable set.
   * @POST: returns one of ↕ | ↑ | ↓.
   */
  function getSortIndicator(column) {
    if (sortColumn !== column) return "↕";
    return sortDirection === "asc" ? "↑" : "↓";
  }

  // [/DEF:DashboardHub.getSortIndicator:Function]

  // [DEF:DashboardHub.applyGridTransforms:Function]
  /**
   * @PURPOSE: Apply search + column filters + sort + pagination to grid data.
   * @PRE: allDashboards is current source collection.
   * @POST: filteredDashboards/dashboards/total/totalPages are synchronized.
   * @UX_STATE: Loaded -> visible rows reflect all active controls deterministically.
   */
  function applyGridTransforms() {
    const searchText = searchQuery.trim().toLowerCase();
    const nextFiltered = allDashboards
      .filter((dashboard) => {
        if (!searchText) return true;
        const haystack = [
          dashboard.title,
          dashboard.actorLabel,
          getGitSummaryLabel(dashboard),
          getLlmSummaryLabel(dashboard),
          dashboard.changedOnLabel,
        ]
          .filter((value) => Boolean(value))
          .join(" ")
          .toLowerCase();
        return haystack.includes(searchText);
      })
      .filter((dashboard) => doesDashboardPassColumnFilters(dashboard));

    nextFiltered.sort((a, b) => {
      const aValue = getSortValue(a, sortColumn);
      const bValue = getSortValue(b, sortColumn);
      if (aValue < bValue) return sortDirection === "asc" ? -1 : 1;
      if (aValue > bValue) return sortDirection === "asc" ? 1 : -1;
      return 0;
    });

    filteredDashboards = nextFiltered;
    total = filteredDashboards.length;
    totalPages = Math.max(1, Math.ceil(total / pageSize));
    if (currentPage > totalPages) {
      currentPage = totalPages;
    }
    const start = (currentPage - 1) * pageSize;
    const end = start + pageSize;
    dashboards = filteredDashboards.slice(start, end);
    updateSelectionState();
  }
  // [/DEF:DashboardHub.applyGridTransforms:Function]

  // Toggle action dropdown
  function toggleActionDropdown(dashboardId, event) {
    event.stopPropagation();
    openActionDropdown =
      openActionDropdown === dashboardId ? null : dashboardId;
  }

  // Close action dropdown when clicking outside
  function closeActionDropdown() {
    openActionDropdown = null;
  }

  // Handle action click
  async function handleAction(dashboard, action) {
    console.log(
      `[DashboardHub][Action] ${action} on dashboard ${dashboard.title}`,
    );
    closeActionDropdown();

    try {
      if (action === "migrate") {
        // Select only this dashboard for migration
        selectedIds.clear();
        selectedIds.add(dashboard.id);
        showMigrateModal = true;
        targetEnvId = null;
        dbMappings = {};
        availableDbMappings = []; // Reset mappings until target env is selected
        sourceDatabases = [];
        targetDatabases = [];
        isEditingMappings = false;
      } else if (action === "backup") {
        // Select only this dashboard for backup
        selectedIds.clear();
        selectedIds.add(dashboard.id);
        showBackupModal = true;
        backupSchedule = "";
        // Force reactivity by updating selectedIds
        selectedIds = new Set(selectedIds);
      } else if (action === "commit") {
        // Open commit modal or trigger commit task
        // For now, we'll trigger a commit via git API
        const response = await api.postApi(
          `/git/repositories/${dashboard.id}/commit`,
          {
            message: `Update dashboard ${dashboard.title}`,
          },
        );
        console.log("[DashboardHub][Action] Commit created:", response);
      }
    } catch (err) {
      console.error("[DashboardHub][Coherence:Failed]", err);
      // Error toast is already shown by api.postApi
    }
  }

  // Handle validate - LLM dashboard validation
  async function handleValidate(dashboard) {
    if (validatingIds.has(dashboard.id)) return;

    validatingIds.add(dashboard.id);
    validatingIds = new Set(validatingIds); // Trigger reactivity

    closeActionDropdown();

    try {
      const response = await api.postApi("/tasks", {
        plugin_id: "llm_dashboard_validation",
        params: {
          dashboard_id: dashboard.id.toString(),
          environment_id: selectedEnv,
        },
      });

      console.log("[DashboardHub][Action] Validation task started:", response);

      // Open task drawer if task was created
      if (response.task_id || response.id) {
        const taskId = response.task_id || response.id;
        openDrawerForTask(taskId);
      }
    } catch (err) {
      console.error("[DashboardHub][Coherence:Failed] Validation failed:", err);
      alert(
        $t.dashboard?.validation_start_failed +
          ": " +
          (err.message || $t.dashboard?.unknown_error),
      );
    } finally {
      validatingIds.delete(dashboard.id);
      validatingIds = new Set(validatingIds);
    }
  }

  // Handle target environment change
  async function handleTargetEnvChange(event) {
    targetEnvId = event.target.value;
    if (targetEnvId) {
      await Promise.all([loadDbMappings(), loadDatabases()]);
    } else {
      availableDbMappings = [];
      dbMappings = {};
      sourceDatabases = [];
      targetDatabases = [];
    }
  }

  // Load databases for both environments
  async function loadDatabases() {
    if (!selectedEnv || !targetEnvId) return;
    try {
      const [sourceRes, targetRes] = await Promise.all([
        api.getEnvironmentDatabases(selectedEnv),
        api.getEnvironmentDatabases(targetEnvId),
      ]);
      sourceDatabases = sourceRes || [];
      targetDatabases = targetRes || [];
    } catch (err) {
      console.error(
        "[DashboardHub][Coherence:Failed] Failed to load databases:",
        err,
      );
    }
  }

  // Handle mapping update from MappingTable
  function handleMappingUpdate(event) {
    const { sourceUuid, targetUuid } = event.detail;
    dbMappings[sourceUuid] = targetUuid;
    dbMappings = dbMappings; // Trigger reactivity
  }

  // Load database mappings for migration modal
  async function loadDbMappings() {
    if (!selectedEnv || !targetEnvId) return;

    try {
      console.log(
        `[DashboardHub][Action] Loading DB mappings from ${selectedEnv} to ${targetEnvId}`,
      );
      const response = await api.getDatabaseMappings(selectedEnv, targetEnvId);
      availableDbMappings = response.mappings || [];
      console.log(
        `[DashboardHub][Action] Loaded mappings:`,
        availableDbMappings,
      );

      // Auto-select mappings with high confidence
      dbMappings = {};
      availableDbMappings.forEach((m) => {
        if (m.confidence > 0.8) {
          dbMappings[m.source_db_uuid] = m.target_db_uuid;
        }
      });
    } catch (err) {
      console.error(
        "[DashboardHub][Coherence:Failed] Failed to load DB mappings:",
        err,
      );
      availableDbMappings = [];
    }
  }

  // Handle bulk migrate
  async function handleBulkMigrate() {
    if (isSubmittingMigrate) return;
    if (selectedIds.size === 0) return;
    if (!targetEnvId) {
      alert($t.dashboard?.target_env_required);
      return;
    }

    isSubmittingMigrate = true;
    try {
      const response = await api.postApi("/dashboards/migrate", {
        source_env_id: selectedEnv,
        target_env_id: targetEnvId,
        dashboard_ids: Array.from(selectedIds),
        db_mappings: useDbMappings ? dbMappings : {},
        replace_db_config: useDbMappings && Object.keys(dbMappings).length > 0,
        fix_cross_filters: fixCrossFilters,
      });
      console.log(
        "[DashboardHub][Action] Bulk migration task created:",
        response.task_id,
      );

      // Close modal and open task drawer
      showMigrateModal = false;
      selectedIds.clear();
      updateSelectionState();

      if (response.task_id) {
        const taskId =
          typeof response.task_id === "object"
            ? response.task_id.id || response.task_id.task_id
            : response.task_id;
        openDrawerForTask(taskId);
      }
    } catch (err) {
      console.error("[DashboardHub][Coherence:Failed]", err);
      alert($t.dashboard?.migration_task_failed);
    } finally {
      isSubmittingMigrate = false;
    }
  }

  // Handle bulk backup
  async function handleBulkBackup() {
    if (isSubmittingBackup) return;
    if (selectedIds.size === 0) return;

    isSubmittingBackup = true;
    try {
      const response = await api.postApi("/dashboards/backup", {
        env_id: selectedEnv,
        dashboard_ids: Array.from(selectedIds),
        schedule: backupSchedule || undefined,
      });
      console.log(
        "[DashboardHub][Action] Bulk backup task created:",
        response.task_id,
      );

      // Close modal and open task drawer
      showBackupModal = false;
      selectedIds.clear();
      updateSelectionState();

      if (response.task_id) {
        const taskId =
          typeof response.task_id === "object"
            ? response.task_id.id || response.task_id.task_id
            : response.task_id;
        openDrawerForTask(taskId);
      }
    } catch (err) {
      console.error("[DashboardHub][Coherence:Failed]", err);
      alert($t.dashboard?.backup_task_failed);
    } finally {
      isSubmittingBackup = false;
    }
  }

  // Handle task status click - open Task Drawer
  function handleTaskStatusClick(dashboard) {
    if (dashboard.lastTask?.id) {
      console.log(
        `[DashboardHub][Action] Open task drawer for task ${dashboard.lastTask.id}`,
      );
      openDrawerForTask(dashboard.lastTask.id);
    }
  }

  function navigateToDashboardDetail(dashboardId) {
    goto(
      `/dashboards/${dashboardId}?env_id=${encodeURIComponent(selectedEnv)}`,
    );
  }

  // Get status badge class
  function getStatusBadgeClass(status) {
    if (!status) return "bg-gray-100 text-gray-800";
    switch (status.toLowerCase()) {
      case "ok":
        return "bg-green-100 text-green-800";
      case "diff":
        return "bg-yellow-100 text-yellow-800";
      default:
        return "bg-gray-100 text-gray-800";
    }
  }

  function isGitBusy(dashboardId) {
    return gitBusyIds.has(dashboardId);
  }

  function setGitBusy(dashboardId, busy) {
    if (busy) {
      gitBusyIds.add(dashboardId);
    } else {
      gitBusyIds.delete(dashboardId);
    }
    gitBusyIds = new Set(gitBusyIds);
  }

  async function ensureGitConfigs() {
    if (cachedGitConfigs.length > 0) return cachedGitConfigs;
    cachedGitConfigs = await gitService.getConfigs();
    return cachedGitConfigs;
  }

  function updateDashboardGitState(dashboardId, nextGit) {
    dashboards = dashboards.map((dashboard) =>
      dashboard.id === dashboardId
        ? { ...dashboard, git: { ...dashboard.git, ...nextGit } }
        : dashboard,
    );
  }

  async function refreshDashboardGitState(dashboardId) {
    try {
      const status = await gitService.getStatus(dashboardId);
      updateDashboardGitState(dashboardId, {
        status: status?.is_dirty ? "diff" : "ok",
        branch: status?.current_branch || null,
        hasRepo: true,
        hasChangesForCommit: Boolean(status?.is_dirty),
      });
    } catch (_err) {
      updateDashboardGitState(dashboardId, {
        status: "no_repo",
        branch: null,
        hasRepo: false,
        hasChangesForCommit: false,
      });
    }
  }

  async function handleGitInit(dashboard) {
    setGitBusy(dashboard.id, true);
    try {
      const configs = await ensureGitConfigs();
      if (!configs.length) {
        addToast(
          $t.git?.no_servers_configured || "No Git config found",
          "error",
        );
        return;
      }

      const config = configs[0];
      const defaultRemote = config?.default_repository
        ? `${String(config.url || "").replace(/\/$/, "")}/${config.default_repository}.git`
        : "";
      const remoteUrl = prompt(
        $t.git?.remote_url || "Remote URL",
        defaultRemote,
      );
      if (!remoteUrl) return;

      await gitService.initRepository(
        dashboard.id,
        config.id,
        remoteUrl.trim(),
      );
      addToast($t.git?.init_success || "Repository initialized", "success");
      await refreshDashboardGitState(dashboard.id);
    } catch (err) {
      addToast(err?.message || "Git init failed", "error");
    } finally {
      setGitBusy(dashboard.id, false);
    }
  }

  async function handleGitSync(dashboard) {
    setGitBusy(dashboard.id, true);
    try {
      await gitService.sync(dashboard.id, selectedEnv || null);
      addToast($t.git?.sync_success || "Synced", "success");
      await refreshDashboardGitState(dashboard.id);
    } catch (err) {
      addToast(err?.message || "Git sync failed", "error");
    } finally {
      setGitBusy(dashboard.id, false);
    }
  }

  async function handleGitCommit(dashboard) {
    if (!dashboard.git?.hasRepo) {
      addToast($t.git?.not_linked || "Repository not linked", "error");
      return;
    }
    if (!dashboard.git?.hasChangesForCommit) {
      addToast($t.git?.nothing_to_commit || "No changes to commit", "error");
      return;
    }

    const message = prompt(
      $t.git?.commit_message || "Commit message",
      `Update dashboard ${dashboard.title}`,
    );
    if (!message?.trim()) return;

    setGitBusy(dashboard.id, true);
    try {
      await gitService.commit(dashboard.id, message.trim());
      addToast($t.git?.commit_success || "Committed", "success");
      await refreshDashboardGitState(dashboard.id);
    } catch (err) {
      addToast(err?.message || "Git commit failed", "error");
    } finally {
      setGitBusy(dashboard.id, false);
    }
  }

  async function handleGitPull(dashboard) {
    if (!dashboard.git?.hasRepo) return;
    setGitBusy(dashboard.id, true);
    try {
      await gitService.pull(dashboard.id);
      addToast($t.git?.pull_success || "Pulled", "success");
      await refreshDashboardGitState(dashboard.id);
    } catch (err) {
      addToast(err?.message || "Git pull failed", "error");
    } finally {
      setGitBusy(dashboard.id, false);
    }
  }

  async function handleGitPush(dashboard) {
    if (!dashboard.git?.hasRepo) return;
    setGitBusy(dashboard.id, true);
    try {
      await gitService.push(dashboard.id);
      addToast($t.git?.push_success || "Pushed", "success");
      await refreshDashboardGitState(dashboard.id);
    } catch (err) {
      addToast(err?.message || "Git push failed", "error");
    } finally {
      setGitBusy(dashboard.id, false);
    }
  }

  // Get task status icon
  function getTaskStatusIcon(status) {
    if (!status) return "";
    switch (status.toLowerCase()) {
      case "running":
        return '<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 0 0 1-8 8z"/></svg>';
      case "pending":
        return '<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 0 0 1-8 8z"/></svg>';
      case "success":
        return '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>';
      case "failed":
      case "error":
        return '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>';
      case "awaiting_input":
      case "waiting_input":
        return '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>';
      default:
        return "";
    }
  }

  // Generate pagination range with ellipsis
  function getPaginationRange(current, total, maxVisible = 5) {
    if (total <= maxVisible) {
      return Array.from({ length: total }, (_, i) => i + 1);
    }

    const range = [];
    const halfVisible = Math.floor(maxVisible / 2);

    let start = Math.max(1, current - halfVisible);
    let end = Math.min(total, current + halfVisible);

    if (current <= halfVisible + 1) {
      end = maxVisible;
    } else if (current >= total - halfVisible) {
      start = total - maxVisible + 1;
    }

    // Add first page and ellipsis if needed
    if (start > 1) {
      range.push(1);
      if (start > 2) {
        range.push("...");
      }
    }

    // Add visible pages
    for (let i = start; i <= end; i++) {
      range.push(i);
    }

    // Add ellipsis and last page if needed
    if (end < total) {
      if (end < total - 1) {
        range.push("...");
      }
      range.push(total);
    }

    return range;
  }

  $: environments = $environmentContextStore?.environments || [];
  $: if (
    $environmentContextStore?.selectedEnvId &&
    selectedEnv !== $environmentContextStore.selectedEnvId
  ) {
    selectedEnv = $environmentContextStore.selectedEnvId;
    currentPage = 1;
    selectedIds.clear();
    loadDashboards();
  }
</script>

<div class="mx-auto w-full max-w-7xl space-y-6">
  <!-- Header -->
  <div class="flex items-center justify-between">
    <h1 class="text-2xl font-bold text-gray-900">
      {$t.nav?.dashboards}
    </h1>
    <div class="flex items-center space-x-4">
      <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"
        on:click={loadDashboards}
      >
        {$t.common?.refresh}
      </button>
    </div>
  </div>

  <!-- Error Banner -->
  {#if error}
    <div
      class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 flex items-center justify-between"
    >
      <span>{error}</span>
      <button
        class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors"
        on:click={loadDashboards}
      >
        {$t.common?.retry}
      </button>
    </div>
  {/if}

  <!-- Loading State -->
  {#if isLoading}
    <div class="bg-white border border-slate-200 rounded-xl shadow-sm overflow-hidden">
      <div
        class="grid min-w-[1520px] gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
        style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
      >
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
      </div>
      {#each Array(5) as _}
        <div
          class="grid min-w-[1520px] gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors"
          style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
        >
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
          <div class="h-4 rounded bg-gray-200 animate-pulse"></div>
        </div>
      {/each}
    </div>
  {:else if dashboards.length === 0}
    <!-- Empty State -->
    <div class="py-12 text-center text-gray-500">
      <svg
        class="w-16 h-16 mx-auto mb-4 text-gray-400"
        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>
      <p>{$t.dashboard?.empty}</p>
    </div>
  {:else}
    <!-- Toolbar -->
    <div class="flex items-center justify-between mb-4 gap-4">
      <div class="flex items-center gap-2">
        <button
          class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
          on:click={handleSelectAll}
          disabled={total === 0}
        >
          {isAllSelected
            ? $t.dashboard?.deselect_all
            : $t.dashboard?.select_all}
        </button>
        <button
          class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
          on:click={handleSelectVisible}
          disabled={dashboards.length === 0}
        >
          {isAllVisibleSelected
            ? $t.dashboard?.deselect_visible
            : $t.dashboard?.select_visible}
        </button>
        {#if selectedIds.size > 0}
          <span class="text-sm text-gray-600">
            {($t.dashboard?.selected_count).replace(
              "{count}",
              String(selectedIds.size),
            )}
          </span>
        {/if}
      </div>
      <div>
        <input
          type="text"
          class="px-4 py-2 border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
          placeholder={$t.dashboard?.search}
          on:input={handleSearch}
          value={searchQuery}
        />
      </div>
    </div>

    <!-- Dashboard Grid -->
    <div class="bg-white border border-slate-200 rounded-xl shadow-sm">
      <div class="overflow-x-auto">
        <!-- Grid Header -->
        <div
          class="grid min-w-[1520px] gap-4 px-6 py-3 bg-slate-50 border-b border-slate-200 font-semibold text-[11px] uppercase tracking-wide text-slate-600"
          style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
        >
          <div></div>
          <div class="flex items-center gap-2">
            <button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("title")}>
              {$t.dashboard?.title} {getSortIndicator("title")}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('title') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
                on:click={() => toggleFilterDropdown("title")}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if openFilterColumn === "title"}
                <div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
                  <input
                    type="text"
                    class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
                    placeholder={$t.dashboard?.search}
                    value={columnFilterSearch.title}
                    on:input={(event) => updateColumnFilterSearch("title", event.target.value)}
                  />
                  <div class="mt-2 flex items-center justify-between text-xs">
                    <button class="text-blue-600 hover:underline" on:click={() => selectAllColumnFilterValues("title")}>{$t.dashboard?.select_all}</button>
                    <button class="text-gray-500 hover:underline" on:click={() => clearColumnFilter("title")}>{$t.common?.clear || "Clear"}</button>
                  </div>
                  <div class="mt-2 max-h-44 overflow-auto space-y-1 text-xs">
                    {#each getVisibleFilterOptions("title") as value}
                      <label class="flex items-center gap-2">
                        <input
                          type="checkbox"
                          checked={columnFilters.title.has(value)}
                          on:change={(event) => toggleFilterValue("title", value, event.target.checked)}
                        />
                        <span class="truncate">{value}</span>
                      </label>
                    {/each}
                  </div>
                </div>
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("git_status")}>
              GIT {getSortIndicator("git_status")}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('git_status') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
                on:click={() => toggleFilterDropdown("git_status")}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if openFilterColumn === "git_status"}
                <div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
                  <input
                    type="text"
                    class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
                    placeholder={$t.dashboard?.search}
                    value={columnFilterSearch.git_status}
                    on:input={(event) => updateColumnFilterSearch("git_status", event.target.value)}
                  />
                  <div class="mt-2 flex items-center justify-between text-xs">
                    <button class="text-blue-600 hover:underline" on:click={() => selectAllColumnFilterValues("git_status")}>{$t.dashboard?.select_all}</button>
                    <button class="text-gray-500 hover:underline" on:click={() => clearColumnFilter("git_status")}>{$t.common?.clear || "Clear"}</button>
                  </div>
                  <div class="mt-2 max-h-44 overflow-auto space-y-1 text-xs">
                    {#each getVisibleFilterOptions("git_status") as value}
                      <label class="flex items-center gap-2">
                        <input
                          type="checkbox"
                          checked={columnFilters.git_status.has(value)}
                          on:change={(event) => toggleFilterValue("git_status", value, event.target.checked)}
                        />
                        <span class="truncate">{value}</span>
                      </label>
                    {/each}
                  </div>
                </div>
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("llm_status")}>
              {$t.dashboard?.llm_status} {getSortIndicator("llm_status")}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('llm_status') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
                on:click={() => toggleFilterDropdown("llm_status")}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if openFilterColumn === "llm_status"}
                <div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
                  <input
                    type="text"
                    class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
                    placeholder={$t.dashboard?.search}
                    value={columnFilterSearch.llm_status}
                    on:input={(event) => updateColumnFilterSearch("llm_status", event.target.value)}
                  />
                  <div class="mt-2 flex items-center justify-between text-xs">
                    <button class="text-blue-600 hover:underline" on:click={() => selectAllColumnFilterValues("llm_status")}>{$t.dashboard?.select_all}</button>
                    <button class="text-gray-500 hover:underline" on:click={() => clearColumnFilter("llm_status")}>{$t.common?.clear || "Clear"}</button>
                  </div>
                  <div class="mt-2 max-h-44 overflow-auto space-y-1 text-xs">
                    {#each getVisibleFilterOptions("llm_status") as value}
                      <label class="flex items-center gap-2">
                        <input
                          type="checkbox"
                          checked={columnFilters.llm_status.has(value)}
                          on:change={(event) => toggleFilterValue("llm_status", value, event.target.checked)}
                        />
                        <span class="truncate">{value}</span>
                      </label>
                    {/each}
                  </div>
                </div>
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("changed_on")}>
              {$t.dashboard?.changed_on} {getSortIndicator("changed_on")}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('changed_on') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
                on:click={() => toggleFilterDropdown("changed_on")}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if openFilterColumn === "changed_on"}
                <div class="absolute z-30 mt-2 w-64 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
                  <input
                    type="text"
                    class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
                    placeholder={$t.dashboard?.search}
                    value={columnFilterSearch.changed_on}
                    on:input={(event) => updateColumnFilterSearch("changed_on", event.target.value)}
                  />
                  <div class="mt-2 flex items-center justify-between text-xs">
                    <button class="text-blue-600 hover:underline" on:click={() => selectAllColumnFilterValues("changed_on")}>{$t.dashboard?.select_all}</button>
                    <button class="text-gray-500 hover:underline" on:click={() => clearColumnFilter("changed_on")}>{$t.common?.clear || "Clear"}</button>
                  </div>
                  <div class="mt-2 max-h-44 overflow-auto space-y-1 text-xs">
                    {#each getVisibleFilterOptions("changed_on") as value}
                      <label class="flex items-center gap-2">
                        <input
                          type="checkbox"
                          checked={columnFilters.changed_on.has(value)}
                          on:change={(event) => toggleFilterValue("changed_on", value, event.target.checked)}
                        />
                        <span class="truncate">{value}</span>
                      </label>
                    {/each}
                  </div>
                </div>
              {/if}
            </div>
          </div>
          <div class="flex items-center gap-2">
            <button class="hover:text-gray-900 transition-colors" on:click={() => handleSort("actor")}>
              {$t.dashboard?.owners || "Owners"} {getSortIndicator("actor")}
            </button>
            <div class="relative column-filter">
              <button
                class="rounded border px-1.5 py-0.5 text-xs transition-colors {hasColumnFilter('actor') ? 'border-blue-400 text-blue-700 bg-blue-50' : 'border-gray-300 text-gray-500 hover:text-gray-700'}"
                on:click={() => toggleFilterDropdown("actor")}
                title={$t.dashboard?.column_filter}
              >
                ▾
              </button>
              {#if openFilterColumn === "actor"}
                <div class="absolute z-30 mt-2 w-72 rounded-lg border border-gray-200 bg-white shadow-lg p-3">
                  <input
                    type="text"
                    class="w-full rounded border border-gray-300 px-2 py-1 text-xs"
                    placeholder={$t.dashboard?.search}
                    value={columnFilterSearch.actor}
                    on:input={(event) => updateColumnFilterSearch("actor", event.target.value)}
                  />
                  <div class="mt-2 flex items-center justify-between text-xs">
                    <button class="text-blue-600 hover:underline" on:click={() => selectAllColumnFilterValues("actor")}>{$t.dashboard?.select_all}</button>
                    <button class="text-gray-500 hover:underline" on:click={() => clearColumnFilter("actor")}>{$t.common?.clear || "Clear"}</button>
                  </div>
                  <div class="mt-2 max-h-44 overflow-auto space-y-1 text-xs">
                    {#each getVisibleFilterOptions("actor") as value}
                      <label class="flex items-center gap-2">
                        <input
                          type="checkbox"
                          checked={columnFilters.actor.has(value)}
                          on:change={(event) => toggleFilterValue("actor", value, event.target.checked)}
                        />
                        <span class="truncate">{value}</span>
                      </label>
                    {/each}
                  </div>
                </div>
              {/if}
            </div>
          </div>
          <div class="flex items-center">
            {$t.dashboard?.actions}
          </div>
        </div>

        <!-- Grid Rows -->
        {#each dashboards as dashboard}
          <div
            class="grid min-w-[1520px] gap-4 px-6 py-4 border-b border-slate-200 last:border-b-0 hover:bg-slate-50 transition-colors text-[13px]"
            style="grid-template-columns: 40px minmax(250px,2.3fr) minmax(118px,0.9fr) minmax(150px,1fr) minmax(150px,1fr) minmax(300px,2.1fr) minmax(280px,2fr);"
          >
            <!-- Checkbox -->
            <div>
              <input
                type="checkbox"
                checked={selectedIds.has(dashboard.id)}
                on:change={(e) => handleCheckboxChange(dashboard, e)}
              />
            </div>

            <!-- Title -->
            <div class="font-medium text-sm text-slate-900">
              <button
                class="text-left text-blue-700 hover:text-blue-900 hover:underline transition-colors"
                on:click={() => navigateToDashboardDetail(dashboard.id)}
                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
                    ? 'bg-blue-100 text-blue-800'
                    : 'bg-slate-100 text-slate-700'}"
                >
                  {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-yellow-100 text-yellow-800'
                      : 'bg-green-100 text-green-800'}"
                  >
                    {dashboard.git?.hasChangesForCommit
                      ? $t.dashboard?.status_changes || "Diff"
                      : $t.dashboard?.status_no_changes || "Synced"}
                  </span>
                {/if}
              </div>
            </div>

            <!-- LLM Validation Status -->
            <div>
              {#if dashboard.lastTask}
                <div
                  class="inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-xs font-semibold uppercase tracking-wide {getValidationBadgeClass(dashboard.lastTask.validationStatus)}"
                  on:click={() => handleTaskStatusClick(dashboard)}
                  on:keydown={(e) =>
                    (e.key === "Enter" || e.key === " ") &&
                    handleTaskStatusClick(dashboard)}
                  role="button"
                  tabindex="0"
                  aria-label={$t.dashboard?.view_task}
                >
                  {getValidationLabel(dashboard.lastTask.validationStatus)}
                </div>
              {:else}
                <span class="text-gray-400">-</span>
              {/if}
            </div>

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

            <!-- Owners -->
            <div class="text-xs">
              <div class="text-slate-500 text-[10px] uppercase tracking-wide">{$t.dashboard?.owners || "Owners"}</div>
              {#if dashboard.owners?.length > 0}
                <div class="mt-0.5 flex flex-wrap items-center gap-1">
                  {#each dashboard.owners as owner (owner)}
                    <span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-700 truncate max-w-[180px]" title={owner}>
                      {owner}
                    </span>
                  {/each}
                </div>
              {:else}
                <div class="mt-0.5 text-gray-400">-</div>
              {/if}
            </div>

            <!-- Actions -->
            <div class="flex items-center">
            <div class="flex items-center gap-1">
              {#if !dashboard.git?.hasRepo}
                <button
                  class="p-2 rounded border border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 hover:border-emerald-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  on:click={() => handleGitInit(dashboard)}
                  disabled={isGitBusy(dashboard.id)}
                  title={$t.git?.init_repo || "Init Git repository"}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M12 5v14M5 12h14" />
                  </svg>
                </button>
              {:else}
                <button
                  class="p-2 rounded border border-sky-200 bg-sky-50 text-sky-700 hover:bg-sky-100 hover:border-sky-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  on:click={() => handleGitSync(dashboard)}
                  disabled={isGitBusy(dashboard.id)}
                  title={$t.git?.sync || "Sync from Superset"}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M21 12a9 9 0 1 1-3-6.7" />
                    <polyline points="21 3 21 9 15 9" />
                  </svg>
                </button>
                <button
                  class="p-2 rounded border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 hover:border-orange-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  on:click={() => handleGitCommit(dashboard)}
                  disabled={isGitBusy(dashboard.id) ||
                    !dashboard.git?.hasChangesForCommit}
                  title={$t.git?.commit || "Commit"}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M12 3v12" />
                    <path d="M7 10l5 5 5-5" />
                    <path d="M5 21h14" />
                  </svg>
                </button>
                <button
                  class="p-2 rounded border border-cyan-200 bg-cyan-50 text-cyan-700 hover:bg-cyan-100 hover:border-cyan-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  on:click={() => handleGitPull(dashboard)}
                  disabled={isGitBusy(dashboard.id)}
                  title={$t.git?.pull || "Pull"}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M12 3v12" />
                    <path d="M17 10l-5 5-5-5" />
                    <path d="M5 21h14" />
                  </svg>
                </button>
                <button
                  class="p-2 rounded border border-indigo-200 bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:border-indigo-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                  on:click={() => handleGitPush(dashboard)}
                  disabled={isGitBusy(dashboard.id)}
                  title={$t.git?.push || "Push"}
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M12 21V9" />
                    <path d="M7 14l5-5 5 5" />
                    <path d="M5 3h14" />
                  </svg>
                </button>
              {/if}
              <button
                class="p-2 rounded border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                on:click={() => 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="p-2 rounded border border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 hover:border-emerald-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                on:click={() => handleValidate(dashboard)}
                disabled={validatingIds.has(dashboard.id)}
                title={$t.dashboard?.action_validate}
              >
                {#if validatingIds.has(dashboard.id)}
                  <svg
                    class="animate-spin"
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <circle
                      cx="12"
                      cy="12"
                      r="10"
                      stroke-dasharray="32"
                      stroke-dashoffset="12"
                    />
                  </svg>
                {:else}
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                  >
                    <path d="M9 12l2 2 4-4" />
                    <circle cx="12" cy="12" r="10" />
                  </svg>
                {/if}
              </button>
              <button
                class="p-2 rounded border border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100 hover:border-amber-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
                on:click={() => 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>
        </div>
      {/each}
    </div>

    <!-- Pagination -->
    {#if totalPages > 1}
      <div
        class="flex items-center justify-between px-4 py-3 bg-gray-50 border-t border-gray-200"
      >
        <div class="text-sm text-gray-600">
          {($t.dashboard?.showing)
            .replace("{start}", String((currentPage - 1) * pageSize + 1))
            .replace("{end}", String(Math.min(currentPage * pageSize, total)))
            .replace("{total}", String(total))}
        </div>
        <div class="flex items-center gap-1">
          <button
            class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
            on:click={() => handlePageChange(1)}
            disabled={currentPage === 1}
          >
            {$t.common?.first}
          </button>
          <button
            class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
            on:click={() => handlePageChange(currentPage - 1)}
            disabled={currentPage === 1}
          >
            {$t.dashboard?.previous}
          </button>
          {#each getPaginationRange(currentPage, totalPages) as pageNum}
            {#if pageNum === "..."}
              <span class="px-2 py-1 text-gray-500">...</span>
            {:else}
              <button
                class="page-btn {pageNum === currentPage ? 'active' : ''}"
                on:click={() => handlePageChange(pageNum)}
              >
                {pageNum}
              </button>
            {/if}
          {/each}
          <button
            class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
            on:click={() => handlePageChange(currentPage + 1)}
            disabled={currentPage === totalPages}
          >
            {$t.dashboard?.next}
          </button>
          <button
            class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
            on:click={() => handlePageChange(totalPages)}
            disabled={currentPage === totalPages}
          >
            {$t.common?.last}
          </button>
        </div>
        <div>
          <select
            class="px-4 py-2 border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
            value={pageSize}
            on:change={handlePageSizeChange}
          >
            <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 selectedIds.size > 0}
      <div
        class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg p-4 transition-transform transform translate-y-0"
      >
        <div class="flex items-center justify-between max-w-7xl mx-auto">
          <div class="flex items-center gap-4">
            <span class="font-medium">
              ✓ {($t.dashboard?.selected_count).replace(
                "{count}",
                String(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"
              on:click={() => {
                showMigrateModal = true;
                targetEnvId = null;
                availableDbMappings = [];
                dbMappings = {};
                sourceDatabases = [];
                targetDatabases = [];
                isEditingMappings = false;
              }}
            >
              {$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"
              on:click={() => (showBackupModal = true)}
            >
              {$t.dashboard?.bulk_backup}
            </button>
            <button
              class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
              on:click={() => selectedIds.clear()}
            >
              {$t.common?.cancel}
            </button>
          </div>
        </div>
      </div>
    {/if}
  {/if}

  <!-- Migration Modal -->
  {#if showMigrateModal}
    <div
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
      on:click={() => (showMigrateModal = false)}
      on:keydown={(e) => e.key === "Escape" && (showMigrateModal = false)}
      role="presentation"
    >
      <div
        class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
        on:click|stopPropagation
        role="dialog"
        aria-modal="true"
        tabindex="-1"
        aria-labelledby="migrate-modal-title"
        on:keydown={(e) => e.stopPropagation()}
      >
        <div
          class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
        >
          <h2 id="migrate-modal-title" class="text-xl font-bold">
            {($t.dashboard?.migrate_modal_title).replace(
              "{count}",
              String(selectedIds.size),
            )}
          </h2>
          <button
            on:click={() => (showMigrateModal = false)}
            class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 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-gray-500 mb-1"
                >{$t.migration?.source_env}</label
              >
              <div
                id="source-env"
                class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
              >
                {environments.find((e) => e.id === selectedEnv)?.name ||
                  selectedEnv}
                {$t.dashboard?.read_only}
              </div>
            </div>

            <!-- Target Env -->
            <div>
              <label
                for="target-env"
                class="block text-sm font-medium text-gray-700 mb-2"
                >{$t.migration?.target_env}</label
              >
              <select
                id="target-env"
                class="env-dropdown w-full"
                bind:value={targetEnvId}
                on:change={handleTargetEnvChange}
              >
                <option value="">
                  {$t.dashboard?.target_env_placeholder ||
                    "Select target environment..."}
                </option>
                {#each environments.filter((e) => e.id !== 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-gray-700"
                  >{$t.migration?.database_mappings}</label
                >
                <label class="relative inline-flex items-center cursor-pointer">
                  <input
                    type="checkbox"
                    id="use-db-mappings"
                    bind:checked={useDbMappings}
                    class="sr-only peer"
                  />
                  <div
                    class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 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-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"
                  ></div>
                  <span class="ml-2 text-xs text-gray-500"
                    >{useDbMappings ? $t.common?.on : $t.common?.off}</span
                  >
                </label>
              </div>

              {#if useDbMappings}
                <div class="flex items-center justify-between mb-2">
                  {#if availableDbMappings.length > 0}
                    <button
                      class="text-sm text-blue-600 hover:underline"
                      on:click={() => (isEditingMappings = !isEditingMappings)}
                    >
                      {isEditingMappings
                        ? $t.dashboard?.view_summary
                        : $t.dashboard?.edit_mappings}
                    </button>
                  {/if}
                </div>

                {#if isEditingMappings}
                  <div
                    class="border border-gray-200 rounded-lg overflow-hidden"
                  >
                    <MappingTable
                      {sourceDatabases}
                      {targetDatabases}
                      mappings={Object.entries(dbMappings).map(([s, t]) => ({
                        source_db_uuid: s,
                        target_db_uuid: t,
                      }))}
                      suggestions={availableDbMappings}
                      on:update={handleMappingUpdate}
                    />
                  </div>
                {:else}
                  <div
                    class="border border-gray-200 rounded-lg overflow-hidden"
                  >
                    <table class="w-full text-sm text-left">
                      <thead class="bg-gray-50 border-b border-gray-200">
                        <tr>
                          <th class="px-4 py-2 font-semibold text-gray-700"
                            >{$t.dashboard?.source_database}</th
                          >
                          <th class="px-4 py-2 font-semibold text-gray-700"
                            >{$t.dashboard?.target_database}</th
                          >
                          <th class="px-4 py-2 font-semibold text-gray-700"
                            >{$t.dashboard?.match_percent}</th
                          >
                        </tr>
                      </thead>
                      <tbody>
                        {#if availableDbMappings.length > 0}
                          {#each availableDbMappings as mapping}
                            <tr
                              class="border-b border-gray-200 last:border-b-0"
                            >
                              <td class="px-4 py-2">{mapping.source_db}</td>
                              <td class="px-4 py-2">
                                {#if dbMappings[mapping.source_db_uuid]}
                                  {targetDatabases.find(
                                    (d) =>
                                      d.uuid ===
                                      dbMappings[mapping.source_db_uuid],
                                  )?.database_name || mapping.target_db}
                                {:else}
                                  <span class="text-red-500"
                                    >{$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-green-100 text-green-800'
                                    : 'bg-yellow-100 text-yellow-800'}"
                                >
                                  {Math.round(mapping.confidence * 100)}%
                                </span>
                              </td>
                            </tr>
                          {/each}
                        {:else}
                          <tr>
                            <td
                              colspan="3"
                              class="px-4 py-4 text-center text-gray-500 italic"
                            >
                              {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-gray-400 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-blue-50 border border-blue-200 rounded-lg p-4">
              <label class="flex items-start gap-3 cursor-pointer">
                <input
                  type="checkbox"
                  bind:checked={fixCrossFilters}
                  class="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
                />
                <div>
                  <span class="text-sm font-medium text-gray-900"
                    >{$t.dashboard?.fix_cross_filters_title ||
                      "Fix cross-filter bindings"}</span
                  >
                  <p class="text-xs text-gray-500 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>
              <label class="block text-sm font-medium text-gray-700 mb-2"
                >{$t.dashboard?.selected_dashboards}</label
              >
              <div
                class="max-h-40 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 p-2"
              >
                {#each Array.from(selectedIds) as id}
                  {#each allDashboards as d}
                    {#if d.id === id}
                      <div class="flex items-center text-sm py-1 px-2">
                        <span class="text-green-600 mr-2">☑</span>
                        <span class="text-gray-700">{d.title}</span>
                      </div>
                    {/if}
                  {/each}
                {/each}
              </div>
            </div>
          </div>
        </div>
        <div class="px-6 py-4 border-t border-gray-200 flex justify-end gap-3">
          <button
            class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
            on:click={() => (showMigrateModal = false)}
            disabled={isSubmittingMigrate}>{$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"
            on:click={handleBulkMigrate}
            disabled={!targetEnvId ||
              selectedIds.size === 0 ||
              isSubmittingMigrate}
          >
            {isSubmittingMigrate ? $t.dashboard?.starting : $t.migration?.start}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Backup Modal -->
  {#if showBackupModal}
    <div
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
      on:click={() => (showBackupModal = false)}
      on:keydown={(e) => e.key === "Escape" && (showBackupModal = false)}
      role="presentation"
    >
      <div
        class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto"
        on:click|stopPropagation
        role="dialog"
        aria-modal="true"
        tabindex="-1"
        aria-labelledby="backup-modal-title"
        on:keydown={(e) => e.stopPropagation()}
      >
        <div
          class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
        >
          <h2 id="backup-modal-title" class="text-xl font-bold">
            {($t.dashboard?.backup_modal_title).replace(
              "{count}",
              String(selectedIds.size),
            )}
          </h2>
          <button
            on:click={() => (showBackupModal = false)}
            class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 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-gray-500 mb-1"
                >{$t.dashboard?.environment}</label
              >
              <div
                id="backup-env"
                class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
              >
                {environments.find((e) => e.id === selectedEnv)?.name ||
                  selectedEnv}
                {$t.dashboard?.read_only}
              </div>
            </div>

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

            <!-- Schedule -->
            <div>
              <label
                id="backup-schedule-label"
                class="block text-sm font-medium text-gray-700 mb-2"
                >{$t.dashboard?.schedule}</label
              >
              <div class="space-y-3" aria-labelledby="backup-schedule-label">
                <label class="flex items-center">
                  <input
                    type="radio"
                    name="schedule"
                    value=""
                    bind:group={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={backupSchedule}
                    class="mr-2"
                  />
                  <span class="text-sm">{$t.dashboard?.schedule_backup}</span>
                </label>
                {#if backupSchedule !== ""}
                  <div class="ml-6">
                    <label
                      for="cron-expression"
                      class="block text-xs text-gray-500 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={backupSchedule}
                    />
                    <button class="text-xs text-blue-600 hover:underline mt-1"
                      >{$t.dashboard?.cron_help}</button
                    >
                  </div>
                {/if}
              </div>
            </div>
          </div>
        </div>
        <div class="px-6 py-4 border-t border-gray-200 flex justify-end gap-3">
          <button
            class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
            on:click={() => (showBackupModal = false)}
            disabled={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"
            on:click={handleBulkBackup}
            disabled={selectedIds.size === 0 || isSubmittingBackup}
          >
            {isSubmittingBackup
              ? $t.dashboard?.starting
              : $t.dashboard?.start_backup}
          </button>
        </div>
      </div>
    </div>
  {/if}
</div>

<!-- [/DEF:DashboardHub:Page] -->
