<!-- #region MigrationPage [C:4] [TYPE Page] [SEMANTICS sveltekit, migration, dashboard, dry-run, environment, model-first] -->
<!--
@SEMANTICS: migration, dashboard, environment, dry-run, task-resume, model-first
@BRIEF: Migration page — thin rendering layer. ALL logic lives in MigrationModel.
@LAYER UI
@RELATION BINDS_TO -> [MigrationModel]
@RELATION DEPENDS_ON ->[EnvSelector]
@RELATION DEPENDS_ON ->[DashboardGrid]
@RELATION DEPENDS_ON ->[MappingTable]
@RELATION DEPENDS_ON ->[EXT:frontend:TaskRunner]
@RELATION DEPENDS_ON ->[TaskHistory]
@RELATION DEPENDS_ON ->[TaskLogViewer]
@RELATION DEPENDS_ON ->[PasswordPrompt]
@INVARIANT: This component contains NO business logic — it only renders model state and calls model.action().
@RATIONALE First page fully migrated to semantic design tokens (surface-*, border-*, text-*, primary, destructive, success, warning, info). All raw buttons replaced with $lib/ui Button. Serves as the canonical visual reference for all ss-tools pages.
@UX_STATE: [idle] -> Skeleton loader while environments load.
@UX_STATE: [ready] -> Step wizard with env selection, dashboards, options.
@UX_STATE: [loading] -> Skeleton/spinner during API calls (dry-run, DB fetch).
@UX_STATE: [review] -> Dry-run summary with diff cards, risk assessment.
@UX_STATE: [executing] -> Task runner view with WebSocket logs.
@UX_STATE: [error] -> Red banner with error message and dismiss button.
@UX_FEEDBACK: Inline error banner, disabled CTA states, loading spinners.
@UX_RECOVERY: Dismiss error, retry dry-run/migration, resume with passwords.
@UX_REACTIVITY: ALL state from MigrationModel atoms. Password prompt via $effect → checkPasswordPrompt().
@TEST_CONTRACT: [Model state] -> [Rendered UI]
@TEST_SCENARIO: render_with_model -> Component renders step 1 with env selector.
@TEST_SCENARIO: step_navigation -> Buttons call model.goToStep().
@TEST_SCENARIO: password_prompt -> $effect triggers model.checkPasswordPrompt() on AWAITING_INPUT.
@TEST_FIXTURE: migration_dry_run_fixture -> file:backend/tests/fixtures/migration_dry_run_fixture.json
@TEST_EDGE: missing_field -> Validation error surfaced from model.
@TEST_EDGE: invalid_type -> API errors from model reflected in error state.
@TEST_EDGE: external_fail -> Model sets error, component renders error banner.
@TEST_INVARIANT: component_has_no_business_logic -> VERIFIED_BY: [render_with_model, step_navigation]
-->

<script lang="ts">
  import { onMount } from "svelte";
  import EnvSelector from "../../components/EnvSelector.svelte";
  import DashboardGrid from "../../components/DashboardGrid.svelte";
  import MappingTable from "../../components/MappingTable.svelte";
  import TaskRunner from "../../components/TaskRunner.svelte";
  import TaskHistory from "../../components/TaskHistory.svelte";
  import TaskLogViewer from "../../components/TaskLogViewer.svelte";
  import PasswordPrompt from "../../components/PasswordPrompt.svelte";
  import { MigrationModel } from "../../lib/models/MigrationModel.svelte.ts";
  import type {
    DashboardMetadata,
    DashboardSelection,
    MigrationDryRunResult,
  } from "../../types/dashboard";
  import { t } from "$lib/i18n/index.svelte.js";
  import { Button, Card, PageHeader } from "$lib/ui";

  // ── Model: single source of truth ───────────────────────────────
  const model = new MigrationModel();

  // Store bindings (reactive, read from stores for TaskRunner + PasswordPrompt)
  const selectedTaskStore = model.selectedTaskStore;
  let activeTask = $state(null);
  $effect(() => {
    const unsub = selectedTaskStore.subscribe(v => { activeTask = v; });
    return unsub;
  });

  // ── Lifecycle ──────────────────────────────────────────────────
  onMount(() => model.loadEnvironments());

  // Reactive: watch for password-prompt signals from active task
  $effect(() => {
    model.checkPasswordPrompt();
  });

  // @RATIONALE This $effect exists because DashboardGrid uses bind:selectedIds which bypasses model.toggleDashboard().
  // The model self-enforces the invariant via toggleDashboard(), but the bind: path is outside model control.
  // This guard ensures dryRunResult is cleared regardless of the mutation path.
  // @INVARIANT: Dashboard selection change clears stale dry-run result
  $effect(() => {
      const _ = model.selectedDashboardIds; // always track
      if (model.dryRunResult) {
          model.dryRunResult = null;
      }
  });
</script>

<div class="max-w-6xl mx-auto p-4 sm:p-6 lg:p-8">
  <!-- #region MigrationHeader -->
  <div class="flex items-center justify-between mb-6">
    <PageHeader title={$t.nav.migration} />
    <Button
      variant="ghost"
      onclick={() => model.toggleTaskHistory()}
    >
      <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
      </svg>
      {$t.tasks?.recent || "Task History"}
      <svg class="w-4 h-4 transition-transform" class:rotate-180={model.showTaskHistory} fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
      </svg>
    </Button>
  </div>
  <!-- #endregion MigrationHeader -->

  {#if activeTask}
    <!-- #region ActiveTaskSection -->
    <div class="mb-6">
      <div class="flex items-center justify-between mb-4">
        <h2 class="text-lg font-semibold text-text">
          {$t.tasks?.task_label || "Task"}
        </h2>
        <Button variant="secondary" onclick={() => selectedTaskStore.set(null)}>
          {$t.common.cancel}
        </Button>
      </div>
      <TaskRunner />
    </div>
    <!-- #endregion ActiveTaskSection -->
  {:else}
    <!-- #region TaskHistoryPanel -->
    {#if model.showTaskHistory}
      <div class="mb-6 overflow-hidden transition-all">
        <TaskHistory pluginId="superset-migration" />
      </div>
    {/if}
    <!-- #endregion TaskHistoryPanel -->

    <!-- #region StepProgressBar -->
    <div class="mb-8">
      <div class="flex items-center justify-between">
        {#each [
          { step: 1, label: $t.migration?.source_env || "Source" },
          { step: 2, label: $t.migration?.select_dashboards_title || "Dashboards" },
          { step: 3, label: $t.tasks?.summary_report || "Review" },
          { step: 4, label: $t.migration?.start || "Migrate" },
        ] as s}
          <!-- Step indicator buttons — custom styling for step wizard (not standard Button) -->
          <button
            onclick={() => model.goToStep(s.step)}
            disabled={s.step > model.currentStep && !((s.step === 2 && model.stepReady[1]) || (s.step === 3 && model.stepReady[1] && model.stepReady[2]) || (s.step === 4 && model.dryRunResult))}
            class="flex flex-col items-center group"
          >
            <div class={`
              w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold border-2 transition-all
              ${model.currentStep > s.step
                ? 'bg-primary border-primary text-white'
                : model.currentStep === s.step
                  ? 'bg-primary-light border-primary-ring text-primary'
                  : 'bg-surface-card border-border-strong text-text-subtle'
              }
              ${(s.step <= model.currentStep || (s.step === 2 && model.stepReady[1]) || (s.step === 3 && model.stepReady[1] && model.stepReady[2])) ? 'cursor-pointer hover:border-primary-ring' : 'cursor-not-allowed'}
            `}>
              {#if model.currentStep > s.step}
                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" />
                </svg>
              {:else}
                {s.step}
              {/if}
            </div>
            <span class="mt-2 text-xs font-medium hidden sm:block
              ${model.currentStep === s.step ? 'text-primary' : 'text-text-muted'}
            ">{s.label}</span>
          </button>
          {#if s.step < 4}
            <div class="flex-1 mx-2 mb-6">
              <div class="h-0.5 bg-surface-muted rounded">
                <div class="h-0.5 bg-primary rounded transition-all duration-500" style="width: {model.currentStep > s.step ? '100%' : '0%'}"></div>
              </div>
            </div>
          {/if}
        {/each}
      </div>
    </div>
    <!-- #endregion StepProgressBar -->

    <!-- Loading State -->
    {#if model.loading}
      <div class="space-y-6">
        <div class="animate-pulse">
          <div class="h-8 bg-surface-muted rounded w-1/3 mb-4"></div>
          <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div class="h-24 bg-surface-muted rounded-lg"></div>
            <div class="h-24 bg-surface-muted rounded-lg"></div>
          </div>
        </div>
      </div>

    <!-- Error State -->
    {:else if model.error}
      <div class="mb-6 p-4 bg-destructive-light border border-destructive-ring rounded-xl">
        <div class="flex items-start gap-3">
          <div class="flex-shrink-0 w-5 h-5 text-destructive mt-0.5">
            <svg viewBox="0 0 20 20" fill="currentColor">
              <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
            </svg>
          </div>
          <div class="flex-1">
            <p class="text-sm font-medium text-destructive">{model.error}</p>
          </div>
          <Button variant="ghost" size="sm" onclick={() => model.clearError()} aria-label={$t.common?.close || "Close"}>
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
            </svg>
          </Button>
        </div>
      </div>
    {/if}

    <!-- #region Step1: Environment Selection -->
    {#if model.currentStep === 1}
      <Card padding="none" class="mb-6">
        <div class="p-6">
          <div class="flex items-center gap-3 mb-4">
            <span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">1</span>
            <h3 class="text-lg font-semibold text-text">{$t.migration?.source_env || "Source Environment"} → {$t.migration?.target_env || "Target Environment"}</h3>
          </div>
          <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
          <div class="p-4 border border-border rounded-xl bg-surface-card hover:border-primary-ring transition-colors">
            <div class="flex items-center gap-2 mb-3">
              <div class="w-2 h-2 rounded-full bg-success"></div>
              <h3 class="text-sm font-semibold text-text uppercase tracking-wide">{$t.migration?.source_env || "Source"}</h3>
            </div>
            <EnvSelector
              label={$t.migration?.source_env || "Source Environment"}
              selectedId={model.sourceEnvId}
              environments={model.environments}
              onchange={({ id }) => model.selectSourceEnv(id)}
            />
          </div>
          <div class="p-4 border border-border rounded-xl bg-surface-card hover:border-primary-ring transition-colors">
            <div class="flex items-center gap-2 mb-3">
              <div class="w-2 h-2 rounded-full bg-primary"></div>
              <h3 class="text-sm font-semibold text-text uppercase tracking-wide">{$t.migration?.target_env || "Target"}</h3>
            </div>
            <EnvSelector
              label={$t.migration?.target_env || "Target Environment"}
              selectedId={model.targetEnvId}
              environments={model.environments}
              onchange={({ id }) => model.selectTargetEnv(id)}
            />
          </div>
        </div>
        {#if model.sourceEnvId && model.targetEnvId && model.sourceEnvId === model.targetEnvId}
          <div class="mt-4 p-3 bg-warning-light border border-warning-ring rounded-lg">
            <p class="text-sm text-warning flex items-center gap-2">
              <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
              </svg>
              {$t.migration?.different_envs || "Source and target environments must be different."}
            </p>
          </div>
        {/if}
        <div class="mt-6 flex justify-end">
          <Button onclick={() => model.goToStep(2)} disabled={!model.stepReady[1]}>
            {$t.dashboard?.next || "Next"} →
          </Button>
        </div>
        </div>
      </Card>
    {/if}
    <!-- #endregion Step1: Environment Selection -->

    <!-- #region Step2: Dashboard Selection & Options -->
    {#if model.currentStep === 2}
      <Card padding="none" class="mb-6">
        <div class="p-6">
          <div class="flex items-center gap-3 mb-4">
            <span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">2</span>
            <h3 class="text-lg font-semibold text-text">{$t.migration?.select_dashboards_title || "Select Dashboards"}</h3>
          </div>

          {#if model.sourceEnvId}
          <div class="mb-6">
            <DashboardGrid
              dashboards={model.dashboards}
              bind:selectedIds={model.selectedDashboardIds}
              environmentId={model.sourceEnvId}
            />
          </div>
          {:else}
          <p class="text-text-muted italic">
            {$t.dashboard?.select_source || "Select a source environment to view dashboards."}
          </p>
          {/if}

        <!-- Migration Options -->
        <div class="border-t border-border pt-4 mt-2">
          <h4 class="text-sm font-semibold text-text mb-3">{$t.dashboard?.settings || "Options"}</h4>
          <div class="space-y-3">
            <label class="flex items-start gap-3 p-3 border border-border rounded-lg hover:bg-surface-muted cursor-pointer transition-colors">
              <input
                type="checkbox"
                checked={model.fixCrossFilters}
                onchange={(e) => model.setFixCrossFilters(e.currentTarget.checked)}
                class="mt-0.5 h-4 w-4 text-primary focus:ring-primary border-border-strong rounded"
              />
              <div>
                <span class="text-sm font-medium text-text">{$t.migration?.fix_cross_filters || "Fix Cross-Filters"}</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."}</p>
              </div>
            </label>

            <label class="flex items-start gap-3 p-3 border border-border rounded-lg hover:bg-surface-muted cursor-pointer transition-colors">
              <input
                type="checkbox"
                checked={model.replaceDb}
                onchange={(e) => model.setReplaceDb(e.currentTarget.checked)}
                class="mt-0.5 h-4 w-4 text-primary focus:ring-primary border-border-strong rounded"
              />
              <div>
                <span class="text-sm font-medium text-text">{$t.migration?.replace_db || "Replace Database"}</span>
                <p class="text-xs text-text-muted mt-0.5">{$t.dashboard?.mapping_disabled_hint || "Apply database mappings during migration."}</p>
              </div>
            </label>
          </div>
        </div>

        <!-- Database Mapping Section -->
        {#if model.replaceDb}
          <div class="mt-4 p-4 bg-surface-muted border border-border rounded-xl">
            <div class="flex items-center justify-between mb-4">
              <h4 class="text-sm font-semibold text-text">{$t.migration?.database_mappings || "Database Mappings"}</h4>
              <Button
                variant="ghost"
                size="sm"
                onclick={() => model.fetchDatabases()}
                disabled={model.fetchingDbs || !model.sourceEnvId || !model.targetEnvId}
              >
                <svg class="w-3.5 h-3.5" class:animate-spin={model.fetchingDbs} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                </svg>
                {$t.migration?.fetch_dbs || "Fetch Databases"}
              </Button>
            </div>
            {#if model.fetchingDbs}
              <div class="flex items-center gap-2 text-sm text-text-muted">
                <svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                </svg>
                <span>{$t.migration?.loading_dbs || "Loading databases and suggestions..."}</span>
              </div>
            {:else if model.sourceDatabases.length > 0}
              <MappingTable
                sourceDatabases={model.sourceDatabases}
                targetDatabases={model.targetDatabases}
                mappings={model.mappings}
                suggestions={model.suggestions}
                onupdate={(e) => model.saveMapping(e.detail.sourceUuid, e.detail.targetUuid)}
              />
            {:else if model.sourceEnvId && model.targetEnvId}
              <p class="text-sm text-text-muted">{$t.migration?.mapping_hint || "Select environments and click \"Fetch Databases\" to start mapping."}</p>
            {/if}
          </div>
        {/if}

        <div class="mt-6 flex items-center justify-between">
          <Button variant="secondary" onclick={() => model.goToStep(1)}>
            ← {$t.common.back}
          </Button>
          <div class="flex items-center gap-3">
            <Button
              variant="secondary"
              onclick={() => model.calculateDryRun()}
              disabled={!model.stepReady[2] || model.dryRunLoading}
            >
              {#if model.dryRunLoading}
                <svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
              {/if}
              {$t.dashboard?.view_summary || "Dry Run"}
            </Button>
          </div>
        </div>
        </div>
      </Card>
    {/if}
    <!-- #endregion Step2: Dashboard Selection & Options -->

    <!-- #region Step3: Review & Dry-Run Results -->
    {#if model.currentStep === 3}
      <Card padding="none" class="mb-6">
        <div class="p-6">
          <div class="flex items-center gap-3 mb-4">
            <span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">3</span>
            <h3 class="text-lg font-semibold text-text">{$t.tasks?.summary_report || "Review & Validate"}</h3>
          </div>

        {#if model.dryRunResult}
          <!-- Selection Summary -->
          <div class="mb-6 p-4 bg-info-light border border-info-ring rounded-xl">
            <div class="flex items-center gap-2 mb-2">
              <svg class="w-5 h-5 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
              </svg>
              <h3 class="text-sm font-semibold text-info-hover">{$t.dashboard?.selected_dashboards || "Selected Dashboards"}</h3>
            </div>
            <div class="flex flex-wrap gap-2">
              {#each model.dryRunResult.selected_dashboard_titles || [] as title}
                <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-info-light text-info">
                  {title}
                </span>
              {/each}
            </div>
          </div>

          <!-- Diff Summary Cards -->
          <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
            <!-- Dashboards -->
            <div class="p-4 border border-border rounded-xl bg-surface-card">
              <div class="flex items-center gap-2 mb-3">
                <div class="w-8 h-8 rounded-lg bg-info-light flex items-center justify-center">
                  <svg class="w-4 h-4 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
                  </svg>
                </div>
                <h4 class="text-sm font-semibold text-text">{$t.dashboard?.title || "Dashboards"}</h4>
              </div>
              <div class="space-y-1.5 text-sm">
                <div class="flex items-center justify-between">
                  <span class="text-success font-medium">Create</span>
                  <span class="font-semibold text-success">{model.dryRunResult.summary.dashboards.create}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-warning font-medium">Update</span>
                  <span class="font-semibold text-warning">{model.dryRunResult.summary.dashboards.update}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-destructive font-medium">Delete</span>
                  <span class="font-semibold text-destructive">{model.dryRunResult.summary.dashboards.delete}</span>
                </div>
              </div>
            </div>

            <!-- Charts -->
            <div class="p-4 border border-border rounded-xl bg-surface-card">
              <div class="flex items-center gap-2 mb-3">
                <div class="w-8 h-8 rounded-lg bg-primary-light flex items-center justify-center">
                  <svg class="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
                  </svg>
                </div>
                <h4 class="text-sm font-semibold text-text">{$t.dashboard?.charts || "Charts"}</h4>
              </div>
              <div class="space-y-1.5 text-sm">
                <div class="flex items-center justify-between">
                  <span class="text-success font-medium">Create</span>
                  <span class="font-semibold text-success">{model.dryRunResult.summary.charts.create}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-warning font-medium">Update</span>
                  <span class="font-semibold text-warning">{model.dryRunResult.summary.charts.update}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-destructive font-medium">Delete</span>
                  <span class="font-semibold text-destructive">{model.dryRunResult.summary.charts.delete}</span>
                </div>
              </div>
            </div>

            <!-- Datasets -->
            <div class="p-4 border border-border rounded-xl bg-surface-card">
              <div class="flex items-center gap-2 mb-3">
                <div class="w-8 h-8 rounded-lg bg-success-light flex items-center justify-center">
                  <svg class="w-4 h-4 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
                  </svg>
                </div>
                <h4 class="text-sm font-semibold text-text">{$t.dashboard?.linked_resources || "Datasets"}</h4>
              </div>
              <div class="space-y-1.5 text-sm">
                <div class="flex items-center justify-between">
                  <span class="text-success font-medium">Create</span>
                  <span class="font-semibold text-success">{model.dryRunResult.summary.datasets.create}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-warning font-medium">Update</span>
                  <span class="font-semibold text-warning">{model.dryRunResult.summary.datasets.update}</span>
                </div>
                <div class="flex items-center justify-between">
                  <span class="text-destructive font-medium">Delete</span>
                  <span class="font-semibold text-destructive">{model.dryRunResult.summary.datasets.delete}</span>
                </div>
              </div>
            </div>
          </div>

          <!-- Risk Assessment -->
          <div class="mb-6 p-4 border border-border rounded-xl bg-surface-card">
            <div class="flex items-center justify-between mb-4">
              <div class="flex items-center gap-2">
                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01" />
                </svg>
                <h3 class="text-sm font-semibold text-text">{$t.tasks?.issues || "Risk Assessment"}</h3>
              </div>
              <div class="flex items-center gap-3">
                <div class="flex items-center gap-2">
                  <span class="text-xs text-text-muted">Score:</span>
                  <div class={`
                    px-2.5 py-1 rounded-full text-xs font-bold
                    ${model.dryRunResult.risk.score <= 20 ? 'bg-success-light text-success' :
                      model.dryRunResult.risk.score <= 50 ? 'bg-warning-light text-warning' :
                      'bg-destructive-light text-destructive'}
                  `}>
                    {model.dryRunResult.risk.score}/100
                  </div>
                </div>
                <div class={`
                  px-2.5 py-1 rounded-full text-xs font-bold
                  ${model.dryRunResult.risk.level === 'low' ? 'bg-success-light text-success' :
                    model.dryRunResult.risk.level === 'medium' ? 'bg-warning-light text-warning' :
                    'bg-destructive-light text-destructive'}
                `}>
                  {model.dryRunResult.risk.level.toUpperCase()}
                </div>
              </div>
            </div>

            <!-- Risk Progress Bar -->
            <div class="w-full h-2 bg-surface-muted rounded-full mb-4 overflow-hidden">
              <div
                class="h-full rounded-full transition-all duration-700"
                class:bg-success={model.dryRunResult.risk.score <= 20}
                class:bg-warning={model.dryRunResult.risk.score > 20 && model.dryRunResult.risk.score <= 50}
                class:bg-destructive={model.dryRunResult.risk.score > 50}
                style="width: {model.dryRunResult.risk.score}%"
              ></div>
            </div>

            <!-- Risk Items -->
            {#if model.dryRunResult.risk.items && model.dryRunResult.risk.items.length > 0}
              <div class="space-y-2">
                {#each model.dryRunResult.risk.items as item}
                  <div class="flex items-start gap-3 p-2.5 rounded-lg text-sm
                    ${item.severity === 'high' ? 'bg-destructive-light' :
                      item.severity === 'medium' ? 'bg-warning-light' : 'bg-info-light'}
                  ">
                    <div class={`
                      flex-shrink-0 w-2 h-2 rounded-full mt-1.5
                      ${item.severity === 'high' ? 'bg-destructive' :
                        item.severity === 'medium' ? 'bg-warning' : 'bg-info'}
                    `}></div>
                    <div>
                      <p class="font-medium text-text">{item.message}</p>
                      <p class="text-xs text-text-muted mt-0.5">{item.object_type}: {item.object_uuid}</p>
                    </div>
                  </div>
                {/each}
              </div>
            {:else}
              <p class="text-sm text-text-muted">No issues detected.</p>
            {/if}
          </div>

          <!-- Raw JSON Details -->
          <details class="border border-border rounded-xl overflow-hidden">
            <summary class="px-4 py-3 bg-surface-muted cursor-pointer text-sm font-medium text-text hover:bg-surface-muted transition-colors flex items-center gap-2">
              <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
              </svg>
              {$t.tasks?.diff_preview || "Diff JSON"}
            </summary>
            <pre class="p-4 max-h-72 overflow-auto text-xs bg-surface-muted text-text">{JSON.stringify(model.dryRunResult, null, 2)}</pre>
          </details>
        {:else}
          <div class="flex flex-col items-center justify-center py-8 text-text-subtle">
            <svg class="w-12 h-12 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
            </svg>
            <p class="text-sm">Run a dry-run to see the migration summary here.</p>
          </div>
        {/if}

        <div class="mt-6 flex items-center justify-between border-t border-border pt-6">
          <Button variant="secondary" onclick={() => model.goToStep(2)}>
            ← {$t.common.back}
          </Button>
          <Button
            onclick={() => {
              model.goToStep(4);
              model.executeMigration();
            }}
            disabled={!model.dryRunResult}
          >
            {$t.migration?.start || "Start Migration"}
          </Button>
        </div>
        </div>
      </Card>
    {/if}
    <!-- #endregion Step3: Review & Dry-Run Results -->

    <!-- #region Step4: Migration in Progress (handled by ActiveTaskSection above) -->
    {#if model.currentStep === 4 && !activeTask}
      <Card padding="none" class="mb-6">
        <div class="flex flex-col items-center justify-center p-12">
          <svg class="w-16 h-16 text-info mb-4 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z" />
          </svg>
          <h3 class="text-lg font-semibold text-text mb-2">{$t.migration?.starting || "Starting Migration..."}</h3>
          <p class="text-sm text-text-muted">{$t.common.loading || "Please wait..."}</p>
          <Button variant="secondary" class="mt-6" onclick={() => model.goToStep(3)}>
            ← {$t.common.back}
          </Button>
        </div>
      </Card>
    {/if}
    <!-- #endregion Step4: Migration in Progress -->

    <!-- Quick Actions when no step is active -->
    {#if !model.currentStep || model.currentStep === 0}
      <Card class="mb-6">
        <div class="flex flex-col items-center justify-center py-12 text-text-subtle">
          <svg class="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
          </svg>
          <p class="text-sm">{$t.dashboard?.migrate_modal_title || "Select environments to start"}</p>
          <Button variant="ghost" size="sm" class="mt-4" onclick={() => model.goToStep(1)}>
            {$t.migration?.source_env || "Get Started"} →
          </Button>
        </div>
      </Card>
    {/if}
  {/if}
</div>

<!-- #region MigrationModals -->
<TaskLogViewer
  show={model.showLogViewer}
  taskId={model.logViewerTaskId}
  taskStatus={model.logViewerTaskStatus}
  onclose={() => model.closeLogViewer()}
/>

<PasswordPrompt
  show={model.showPasswordPrompt}
  databases={model.passwordPromptDatabases}
  errorMessage={model.passwordPromptErrorMessage}
  onresume={(e) => model.resumeMigration(e.detail.passwords)}
  oncancel={() => model.showPasswordPrompt = false}
/>
<!-- #endregion MigrationModals -->

<!-- #endregion MigrationPage -->