<!-- [DEF:frontend/src/routes/migration/+page.svelte:Module] -->
<!--
@TIER: CRITICAL
@SEMANTICS: migration, dashboard, environment, selection, database-replacement, dry-run, task-resume
@PURPOSE: Main migration dashboard page for environment selection, dry-run validation, and migration execution.
@LAYER: UI
@RELATION: [DEPENDS_ON] ->[frontend/src/lib/api.js]
@RELATION: [DEPENDS_ON] ->[frontend/src/lib/stores.js]
@RELATION: [DEPENDS_ON] ->[frontend/src/services/taskService.js]
@RELATION: [BINDS_TO] ->[frontend/src/components/EnvSelector.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/DashboardGrid.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/MappingTable.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/TaskRunner.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/TaskHistory.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/TaskLogViewer.svelte]
@RELATION: [BINDS_TO] ->[frontend/src/components/PasswordPrompt.svelte]
@INVARIANT: Migration start is blocked unless source and target environments are selected, distinct, and at least one dashboard is selected.
@UX_STATE: [Idle] -> User configures source/target environments, dashboard selection, and migration options.
@UX_STATE: [Loading] -> Environment/database/dry-run fetch operations disable relevant actions and show progress text.
@UX_STATE: [Error] -> Error banner/prompt message is shown while keeping user input intact for correction.
@UX_STATE: [Success] -> Dry-run summary or active task view is rendered after successful API operations.
@UX_FEEDBACK: Inline error banner, disabled CTA states, loading labels, dry-run summary cards, modal dialogs.
@UX_RECOVERY: User can adjust selection, refresh databases, retry dry-run/migration, resume task with passwords, or cancel modal flow.
@UX_REACTIVITY: State transitions rely on Svelte reactive bindings and store subscription to selectedTask.
@TEST_CONTRACT: [DashboardSelection + Environment IDs] -> [DryRunResult | TaskStartResult | ValidationError]
@TEST_SCENARIO: start_migration_valid_selection -> Starts backend task and switches to task view.
@TEST_SCENARIO: start_dry_run_valid_selection -> Renders pre-flight diff summary and risk panel.
@TEST_SCENARIO: awaiting_input_task_selected -> Opens password prompt with requested databases.
@TEST_FIXTURE: migration_dry_run_fixture -> file:backend/tests/fixtures/migration_dry_run_fixture.json
@TEST_EDGE: missing_field -> Empty source/target/selection blocks action and surfaces validation message.
@TEST_EDGE: invalid_type -> Malformed API payload is surfaced through error state.
@TEST_EDGE: external_fail -> API failures set error state and preserve recoverable UI controls.
@TEST_INVARIANT: migration_guardrails_enforced -> VERIFIED_BY: [start_migration_valid_selection, start_dry_run_valid_selection, missing_field]
-->

<!-- [DEF:MigrationDashboard:Component] -->
<!--
@PURPOSE: Orchestrate migration UI workflow and route user actions to backend APIs and task store.
@PRE: API client and component dependencies are available; i18n store is initialized.
@POST: User can progress through selection, dry-run, migration start, and task resume flows.
@SIDE_EFFECT: Performs HTTP requests, mutates local UI state, updates selectedTask store.
@DATA_CONTRACT: DashboardSelection -> MigrationDryRunResult | Task DTO (from /tasks endpoints)
-->

<script lang="ts">
  // [SECTION: IMPORTS]
  import { onMount } from "svelte";
  import { fromStore } from "svelte/store";
  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 { api } from "../../lib/api.js";
  import { selectedTask } from "../../lib/stores.js";
  import { resumeTask } from "../../services/taskService.js";
  import type {
    DashboardMetadata,
    DashboardSelection,
    MigrationDryRunResult,
  } from "../../types/dashboard";
  import { t } from "$lib/i18n";
  import { Button, Card, PageHeader } from "$lib/ui";
  // [/SECTION]

  // [SECTION: STATE]
  let environments: any[] = $state([]);
  let sourceEnvId = $state("");
  let targetEnvId = $state("");
  let replaceDb = $state(false);
  let fixCrossFilters = $state(true);
  let loading = $state(true);
  let error = $state("");
  let dashboards: DashboardMetadata[] = $state([]);
  let selectedDashboardIds: number[] = $state([]);
  let sourceDatabases: any[] = $state([]);
  let targetDatabases: any[] = $state([]);
  let mappings: any[] = $state([]);
  let suggestions: any[] = $state([]);
  let fetchingDbs = $state(false);
  let dryRunLoading = $state(false);
  let dryRunResult: MigrationDryRunResult | null = $state(null);

  // UI State for Modals
  let showLogViewer = $state(false);
  let logViewerTaskId: string | null = $state(null);
  let logViewerTaskStatus: string | null = $state(null);

  let showPasswordPrompt = $state(false);
  let passwordPromptDatabases: string[] = $state([]);
  let passwordPromptErrorMessage = $state("");
  // [/SECTION]

  const selectedTaskState = fromStore(selectedTask);

  const belief_scope = <T>(_id: string, fn: () => T): T => fn();

  // [DEF:fetchEnvironments:Function]
  /**
   * @purpose Fetches the list of environments from the API.
   * @pre     None.
   * @post    environments state is updated.
   */
  async function fetchEnvironments() {
    return belief_scope("fetchEnvironments", async () => {
      console.info("[fetchEnvironments][REASON] Initializing environment list for selection");
      try {
        environments = await api.getEnvironmentsList();
        console.info("[fetchEnvironments][REFLECT] Environments loaded", { count: environments.length });
      } catch (e) {
        console.error("[fetchEnvironments][EXPLORE] Failed to fetch environments", e);
        error = e.message;
      } finally {
        loading = false;
      }
    });
  }
  // [/DEF:fetchEnvironments:Function]

  // [DEF:fetchDashboards:Function]
  /**
   * @purpose Fetches dashboards for the selected source environment.
   * @pre     envId is a valid environment ID.
   * @param envId The environment ID.
   * @post    dashboards state is updated.
   */
  async function fetchDashboards(envId: string) {
    return belief_scope("fetchDashboards", async () => {
      console.info("[fetchDashboards][REASON] Fetching dashboards for environment", { envId });
      try {
        dashboards = await api.requestApi(`/environments/${envId}/dashboards`);
        selectedDashboardIds = []; // Reset selection when env changes
        console.info("[fetchDashboards][REFLECT] Dashboards loaded", { count: dashboards.length });
      } catch (e) {
        console.error("[fetchDashboards][EXPLORE] Failed to fetch dashboards", e);
        error = e.message;
        dashboards = [];
      }
    });
  }
  // [/DEF:fetchDashboards:Function]

  onMount(fetchEnvironments);

  // [DEF:ReactiveDashboardFetch:Block]
  /**
   * @PURPOSE: Automatically fetch dashboards when the source environment is changed.
   * @PRE: sourceEnvId is not empty.
   * @POST: fetchDashboards is called with the new sourceEnvId.
   * @UX_STATE: [Loading] -> Triggered when sourceEnvId changes.
   */
  $effect(() => {
    if (!sourceEnvId) return;
    console.info("[ReactiveDashboardFetch][REASON] Source environment changed, fetching dashboards", { sourceEnvId });
    void fetchDashboards(sourceEnvId);
  });
  // [/DEF:ReactiveDashboardFetch:Block]

  // [DEF:fetchDatabases:Function]
  /**
   * @purpose Fetches databases from both environments and gets suggestions.
   * @pre     sourceEnvId and targetEnvId must be set.
   * @post    sourceDatabases, targetDatabases, mappings, and suggestions are updated.
   */
  async function fetchDatabases() {
    return belief_scope("fetchDatabases", async () => {
      if (!sourceEnvId || !targetEnvId) {
        console.warn("[fetchDatabases][EXPLORE] Missing environment IDs for database fetch");
        return;
      }
      console.info("[fetchDatabases][REASON] Fetching databases and suggestions for mapping", { sourceEnvId, targetEnvId });
      fetchingDbs = true;
      error = "";

      try {
        const [src, tgt, maps, sugs] = await Promise.all([
          api.requestApi(`/environments/${sourceEnvId}/databases`),
          api.requestApi(`/environments/${targetEnvId}/databases`),
          api.requestApi(
            `/mappings?source_env_id=${sourceEnvId}&target_env_id=${targetEnvId}`,
          ),
          api.postApi(`/mappings/suggest`, {
            source_env_id: sourceEnvId,
            target_env_id: targetEnvId,
          }),
        ]);

        sourceDatabases = src;
        targetDatabases = tgt;
        mappings = maps;
        suggestions = sugs;
        console.info("[fetchDatabases][REFLECT] Databases and mappings loaded", {
          sourceCount: src.length,
          targetCount: tgt.length,
          mappingCount: maps.length
        });
      } catch (e) {
        console.error("[fetchDatabases][EXPLORE] Failed to fetch databases", e);
        error = e.message;
      } finally {
        fetchingDbs = false;
      }
    });
  }
  // [/DEF:fetchDatabases:Function]

  // [DEF:handleMappingUpdate:Function]
  /**
   * @purpose Saves a mapping to the backend.
   * @pre     event.detail contains sourceUuid and targetUuid.
   * @post    Mapping is saved and local mappings list is updated.
   */
  async function handleMappingUpdate(event: CustomEvent) {
    return belief_scope("handleMappingUpdate", async () => {
      const { sourceUuid, targetUuid } = event.detail;
      const sDb = sourceDatabases.find((d) => d.uuid === sourceUuid);
      const tDb = targetDatabases.find((d) => d.uuid === targetUuid);

      if (!sDb || !tDb) {
        console.warn("[handleMappingUpdate][EXPLORE] Database not found for mapping", { sourceUuid, targetUuid });
        return;
      }

      console.info("[handleMappingUpdate][REASON] Updating database mapping", { sourceUuid, targetUuid });
      try {
        const savedMapping = await api.postApi("/mappings", {
          source_env_id: sourceEnvId,
          target_env_id: targetEnvId,
          source_db_uuid: sourceUuid,
          target_db_uuid: targetUuid,
          source_db_name: sDb.database_name,
          target_db_name: tDb.database_name,
        });

        mappings = [
          ...mappings.filter((m) => m.source_db_uuid !== sourceUuid),
          savedMapping,
        ];
        console.info("[handleMappingUpdate][REFLECT] Mapping saved successfully");
      } catch (e) {
        console.error("[handleMappingUpdate][EXPLORE] Failed to save mapping", e);
        error = e.message;
      }
    });
  }
  // [/DEF:handleMappingUpdate:Function]

  // [DEF:handleViewLogs:Function]
  // @PURPOSE: Opens the log viewer for a specific task.
  // @PRE: event.detail contains task object.
  // @POST: logViewer state updated and showLogViewer set to true.
  function handleViewLogs(event: CustomEvent) {
    return belief_scope("handleViewLogs", () => {
      const task = event.detail;
      logViewerTaskId = task.id;
      logViewerTaskStatus = task.status;
      showLogViewer = true;
    });
  }
  // [/DEF:handleViewLogs:Function]

  // [DEF:handlePasswordPrompt:Function]
  // @PURPOSE: Reactive logic to show password prompt when a task is awaiting input.
  // @PRE: selectedTask status is AWAITING_INPUT.
  // @POST: showPasswordPrompt set to true with request data.
  // This is triggered by TaskRunner or TaskHistory when a task needs input
  // For now, we rely on the WebSocket or manual check.
  // Ideally, TaskHistory or TaskRunner emits an event when input is needed.
  // Or we watch selectedTask.

  // [DEF:ReactivePasswordPrompt:Block]
  /**
   * @PURPOSE: Monitor selected task for input requests and trigger password prompt.
   * @PRE: $selectedTask is not null and status is AWAITING_INPUT.
   * @POST: showPasswordPrompt is set to true if input_request is database_password.
   * @UX_STATE: [AwaitingInput] -> Password prompt modal is displayed.
   */
  $effect(() => {
    const activeTask = selectedTaskState.current;
    if (
      !activeTask ||
      activeTask.status !== "AWAITING_INPUT" ||
      !activeTask.input_request
    ) {
      return;
    }
    const req = activeTask.input_request;
    if (req.type === "database_password") {
      console.info("[ReactivePasswordPrompt][REASON] Task awaiting database passwords", { taskId: activeTask.id });
      passwordPromptDatabases = req.databases || [];
      passwordPromptErrorMessage = req.error_message || "";
      showPasswordPrompt = true;
    }
  });
  // [/DEF:ReactivePasswordPrompt:Block]
  // [/DEF:handlePasswordPrompt:Function]

  // [DEF:handleResumeMigration:Function]
  // @PURPOSE: Resumes a migration task with provided passwords.
  // @PRE: event.detail contains passwords.
  // @POST: resumeTask is called and showPasswordPrompt is hidden on success.
  async function handleResumeMigration(event: CustomEvent) {
    return belief_scope("handleResumeMigration", async () => {
      if (!selectedTaskState.current) return;

      const { passwords } = event.detail;
      try {
        await resumeTask(selectedTaskState.current.id, passwords);
        showPasswordPrompt = false;
        // Task status update will be handled by store/websocket
      } catch (e) {
        console.error("Failed to resume task:", e);
        passwordPromptErrorMessage =
          e.message || $t.migration?.resume_failed;
        // Keep prompt open
      }
    });
  }
  // [/DEF:handleResumeMigration:Function]

  // [DEF:startMigration:Function]
  /**
   * @PURPOSE: Initiates the migration process by sending the selection to the backend.
   * @PRE: sourceEnvId and targetEnvId are set and different; at least one dashboard is selected.
   * @POST: A migration task is created and selectedTask store is updated.
   * @SIDE_EFFECT: Resets dryRunResult; updates error state on failure.
   * @UX_STATE: [Loading] -> [Success] or [Error]
   */
  async function startMigration() {
    return belief_scope("startMigration", async () => {
      if (!sourceEnvId || !targetEnvId) {
        console.warn("[startMigration][EXPLORE] Missing environment selection");
        error =
          $t.migration?.select_both_envs ||
          "Please select both source and target environments.";
        return;
      }
      if (sourceEnvId === targetEnvId) {
        console.warn("[startMigration][EXPLORE] Source and target environments are identical");
        error =
          $t.migration?.different_envs ||
          "Source and target environments must be different.";
        return;
      }
      if (selectedDashboardIds.length === 0) {
        console.warn("[startMigration][EXPLORE] No dashboards selected");
        error =
          $t.migration?.select_dashboards ||
          "Please select at least one dashboard to migrate.";
        return;
      }

      console.info("[startMigration][REASON] Initiating migration execution", {
        sourceEnvId,
        targetEnvId,
        dashboardCount: selectedDashboardIds.length
      });
      error = "";
      try {
        dryRunResult = null;
        const selection: DashboardSelection = {
          selected_ids: selectedDashboardIds,
          source_env_id: sourceEnvId,
          target_env_id: targetEnvId,
          replace_db_config: replaceDb,
          fix_cross_filters: fixCrossFilters,
        };
        
        const result = await api.postApi("/migration/execute", selection);
        console.info("[startMigration][REFLECT] Migration task created", { taskId: result.task_id });

        // Wait a brief moment for the backend to ensure the task is retrievable
        await new Promise((r) => setTimeout(r, 500));

        // Fetch full task details and switch to TaskRunner view
        try {
          const task = await api.getTask(result.task_id);
          selectedTask.set(task);
          console.info("[startMigration][REFLECT] Task details fetched and store updated");
        } catch (fetchErr) {
          console.warn("[startMigration][EXPLORE] Could not fetch task details immediately, using placeholder", fetchErr);
          selectedTask.set({
            id: result.task_id,
            plugin_id: "superset-migration",
            status: "RUNNING",
            logs: [],
            params: {},
          });
        }
      } catch (e) {
        console.error("[startMigration][EXPLORE] Migration initiation failed", e);
        error = e.message;
      }
    });
  }
  // [/DEF:startMigration:Function]

  // [DEF:startDryRun:Function]
  /**
   * @PURPOSE: Performs a dry-run migration to identify potential risks and changes.
   * @PRE: source/target environments and selected dashboards are valid.
   * @POST: dryRunResult is populated with the pre-flight analysis.
   * @UX_STATE: [Loading] -> [Success] or [Error]
   * @UX_FEEDBACK: User sees summary cards + risk block + JSON details after success.
   * @UX_RECOVERY: User can adjust selection and press Dry Run again.
   */
  async function startDryRun() {
    return belief_scope("startDryRun", async () => {
      if (!sourceEnvId || !targetEnvId) {
        console.warn("[startDryRun][EXPLORE] Missing environment selection");
        error =
          $t.migration?.select_both_envs ||
          "Please select both source and target environments.";
        return;
      }
      if (sourceEnvId === targetEnvId) {
        console.warn("[startDryRun][EXPLORE] Source and target environments are identical");
        error =
          $t.migration?.different_envs ||
          "Source and target environments must be different.";
        return;
      }
      if (selectedDashboardIds.length === 0) {
        console.warn("[startDryRun][EXPLORE] No dashboards selected");
        error =
          $t.migration?.select_dashboards ||
          "Please select at least one dashboard to migrate.";
        return;
      }

      console.info("[startDryRun][REASON] Initiating dry-run analysis", { sourceEnvId, targetEnvId });
      error = "";
      dryRunLoading = true;
      try {
        const selection: DashboardSelection = {
          selected_ids: selectedDashboardIds,
          source_env_id: sourceEnvId,
          target_env_id: targetEnvId,
          replace_db_config: replaceDb,
          fix_cross_filters: fixCrossFilters,
        };
        dryRunResult = await api.postApi("/migration/dry-run", selection);
        console.info("[startDryRun][REFLECT] Dry-run analysis completed", { riskScore: dryRunResult.risk.score });
      } catch (e) {
        console.error("[startDryRun][EXPLORE] Dry-run analysis failed", e);
        error = e.message;
        dryRunResult = null;
      } finally {
        dryRunLoading = false;
      }
    });
  }
  // [/DEF:startDryRun:Function]
  // [/DEF:MigrationDashboard:Component]
</script>

<!-- [DEF:MigrationDashboardView:Block] -->
<!--
@PURPOSE: Render migration configuration controls, action CTAs, dry-run results, and modal entry points.
@UX_STATE: Idle -> Configuration form is interactive.
@UX_STATE: Loading -> Loading messages and disabled buttons prevent duplicate actions.
@UX_STATE: Error -> Error banner is displayed without discarding current selection.
@UX_STATE: Success -> Dry-run details or TaskRunner content is presented.
@UX_FEEDBACK: Buttons, banners, cards, and dialogs provide immediate operation feedback.
@UX_RECOVERY: User can retry operations from the same page state.
-->
<!-- [SECTION: TEMPLATE] -->
<div class="max-w-4xl mx-auto p-6">
  <!-- [DEF:MigrationHeader:Block] -->
  <PageHeader title={$t.nav.migration} />
  <!-- [/DEF:MigrationHeader:Block] -->

  <!-- [DEF:TaskHistorySection:Block] -->
  <TaskHistory />
  <!-- [/DEF:TaskHistorySection:Block] -->

  <!-- [DEF:ActiveTaskSection:Block] -->
  {#if $selectedTask}
    <div class="mt-6">
      <TaskRunner />
      <div class="mt-4">
        <Button variant="secondary" onclick={() => {
          console.info("[ActiveTaskSection][REASON] User cancelled active task view");
          selectedTask.set(null);
        }}>
          {$t.common.cancel}
        </Button>
      </div>
    </div>
  {:else}
  <!-- [/DEF:ActiveTaskSection:Block] -->
    {#if loading}
      <p>{$t.migration?.loading_envs }</p>
    {:else if error}
      <div
        class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"
      >
        {error}
      </div>
    {/if}

    <!-- [DEF:EnvironmentSelectionSection:Block] -->
    <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
      <EnvSelector
        label={$t.migration?.source_env }
        bind:selectedId={sourceEnvId}
        {environments}
      />
      <EnvSelector
        label={$t.migration?.target_env }
        bind:selectedId={targetEnvId}
        {environments}
      />
    </div>
    <!-- [/DEF:EnvironmentSelectionSection:Block] -->

    <!-- [DEF:DashboardSelectionSection:Component] -->
    <div class="mb-8">
      <h2 class="text-lg font-medium mb-4">
        {$t.migration?.select_dashboards_title }
      </h2>

      {#if sourceEnvId}
        <DashboardGrid
          {dashboards}
          bind:selectedIds={selectedDashboardIds}
          environmentId={sourceEnvId}
        />
      {:else}
        <p class="text-gray-500 italic">
          {$t.dashboard?.select_source ||
            "Select a source environment to view dashboards."}
        </p>
      {/if}
    </div>
    <!-- [/DEF:DashboardSelectionSection:Component] -->

    <!-- [DEF:MigrationOptionsSection:Block] -->
    <div class="mb-4">
      <div class="flex items-center mb-2">
        <input
          id="fix-cross-filters"
          type="checkbox"
          bind:checked={fixCrossFilters}
          class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
        />
        <label for="fix-cross-filters" class="ml-2 block text-sm text-gray-900">
          {$t.migration?.fix_cross_filters ||
            "Fix Cross-Filters (Auto-repair broken links during migration)"}
        </label>
      </div>

      <div class="flex items-center">
        <input
          id="replace-db"
          type="checkbox"
          bind:checked={replaceDb}
          onchange={() => {
            console.info("[MigrationOptionsSection][REASON] Database replacement toggled", { replaceDb });
            if (replaceDb && sourceDatabases.length === 0) fetchDatabases();
          }}
          class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
        />
        <label for="replace-db" class="ml-2 block text-sm text-gray-900">
          {$t.migration?.replace_db }
        </label>
      </div>
    </div>
    <!-- [/DEF:MigrationOptionsSection:Block] -->

    {#if replaceDb}
      <div class="mb-8 p-4 border rounded-md bg-gray-50">
        <h3 class="text-md font-medium mb-4">
          {$t.migration?.database_mappings }
        </h3>
        {#if fetchingDbs}
          <p>
            {$t.migration?.loading_dbs ||
              "Loading databases and suggestions..."}
          </p>
        {:else if sourceDatabases.length > 0}
          <MappingTable
            {sourceDatabases}
            {targetDatabases}
            {mappings}
            {suggestions}
            onupdate={handleMappingUpdate}
          />
        {:else if sourceEnvId && targetEnvId}
          <button
            onclick={fetchDatabases}
            class="text-indigo-600 hover:text-indigo-500 text-sm font-medium"
          >
            {$t.migration?.refresh_dbs }
          </button>
        {/if}
      </div>
    {/if}

    <div class="flex items-center gap-3">
      <Button
        variant="secondary"
        onclick={startDryRun}
        disabled={!sourceEnvId ||
          !targetEnvId ||
          sourceEnvId === targetEnvId ||
          selectedDashboardIds.length === 0 ||
          dryRunLoading}
      >
        {dryRunLoading ? "Dry Run..." : "Dry Run"}
      </Button>

      <Button
        onclick={startMigration}
        disabled={!sourceEnvId ||
          !targetEnvId ||
          sourceEnvId === targetEnvId ||
          selectedDashboardIds.length === 0}
      >
        {$t.migration?.start || "Apply"}
      </Button>
    </div>

    <!-- [DEF:DryRunResultsSection:Block] -->
    {#if dryRunResult}
      <div class="mt-6 rounded-md border border-slate-200 bg-slate-50 p-4 space-y-3">
        <h3 class="text-base font-semibold">Pre-flight Diff</h3>
        <div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
          <div class="rounded border border-slate-200 bg-white p-3">
            <div class="font-medium mb-1">Dashboards</div>
            <div>create: {dryRunResult.summary.dashboards.create}</div>
            <div>update: {dryRunResult.summary.dashboards.update}</div>
            <div>delete: {dryRunResult.summary.dashboards.delete}</div>
          </div>
          <div class="rounded border border-slate-200 bg-white p-3">
            <div class="font-medium mb-1">Charts</div>
            <div>create: {dryRunResult.summary.charts.create}</div>
            <div>update: {dryRunResult.summary.charts.update}</div>
            <div>delete: {dryRunResult.summary.charts.delete}</div>
          </div>
          <div class="rounded border border-slate-200 bg-white p-3">
            <div class="font-medium mb-1">Datasets</div>
            <div>create: {dryRunResult.summary.datasets.create}</div>
            <div>update: {dryRunResult.summary.datasets.update}</div>
            <div>delete: {dryRunResult.summary.datasets.delete}</div>
          </div>
        </div>

        <div class="rounded border border-slate-200 bg-white p-3 text-sm">
          <div class="font-medium mb-1">Risk</div>
          <div>
            score: {dryRunResult.risk.score}, level: {dryRunResult.risk.level}
          </div>
          <div class="mt-1">
            issues: {dryRunResult.risk.items.length}
          </div>
        </div>

        <details class="rounded border border-slate-200 bg-white p-3">
          <summary class="cursor-pointer font-medium">Diff JSON</summary>
          <pre class="mt-2 max-h-72 overflow-auto text-xs">{JSON.stringify(dryRunResult, null, 2)}</pre>
        </details>
      </div>
    {/if}
    <!-- [/DEF:DryRunResultsSection:Block] -->
  {/if}
</div>

<!-- [DEF:MigrationModals:Block] -->
<!--
@PURPOSE: Render overlay components for log viewing and password entry.
@UX_STATE: [LogViewing] -> TaskLogViewer is visible.
@UX_STATE: [AwaitingInput] -> PasswordPrompt is visible.
-->
<TaskLogViewer
  bind:show={showLogViewer}
  taskId={logViewerTaskId}
  taskStatus={logViewerTaskStatus}
  onclose={() => {
    console.info("[MigrationModals][REASON] Closing log viewer");
    showLogViewer = false;
  }}
/>

<PasswordPrompt
  bind:show={showPasswordPrompt}
  databases={passwordPromptDatabases}
  errorMessage={passwordPromptErrorMessage}
  onresume={handleResumeMigration}
  oncancel={() => {
    console.info("[MigrationModals][REASON] User cancelled password prompt");
    showPasswordPrompt = false;
  }}
/>
<!-- [/DEF:MigrationModals:Block] -->

<!-- [/SECTION] -->
<!-- [/DEF:MigrationDashboardView:Block] -->
<!-- [/DEF:frontend/src/routes/migration/+page.svelte:Module] -->
