<!-- [DEF:MigrationDashboard:Component] -->
<!--
@SEMANTICS: migration, dashboard, environment, selection, database-replacement
@PURPOSE:   Main dashboard for configuring and starting migrations.
@LAYER:     Page
@RELATION:  USES -> EnvSelector

@INVARIANT: Migration cannot start without source and target environments.
-->

<script lang="ts">
  // [SECTION: IMPORTS]
  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 { 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[] = [];
  let sourceEnvId = "";
  let targetEnvId = "";
  let replaceDb = false;
  let fixCrossFilters = true;
  let loading = true;
  let error = "";
  let dashboards: DashboardMetadata[] = [];
  let selectedDashboardIds: number[] = [];
  let sourceDatabases: any[] = [];
  let targetDatabases: any[] = [];
  let mappings: any[] = [];
  let suggestions: any[] = [];
  let fetchingDbs = false;
  let dryRunLoading = false;
  let dryRunResult: MigrationDryRunResult | null = null;

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

  let showPasswordPrompt = false;
  let passwordPromptDatabases: string[] = [];
  let passwordPromptErrorMessage = "";
  // [/SECTION]

  // [DEF:fetchEnvironments:Function]
  /**
   * @purpose Fetches the list of environments from the API.
   * @pre     None.
   * @post    environments state is updated.
   */
  async function fetchEnvironments() {
    try {
      environments = await api.getEnvironmentsList();
    } catch (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) {
    try {
      dashboards = await api.requestApi(`/environments/${envId}/dashboards`);
      selectedDashboardIds = []; // Reset selection when env changes
    } catch (e) {
      error = e.message;
      dashboards = [];
    }
  }
  // [/DEF:fetchDashboards:Function]

  onMount(fetchEnvironments);

  // Reactive: fetch dashboards when source env changes
  $: if (sourceEnvId) fetchDashboards(sourceEnvId);

  // [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() {
    if (!sourceEnvId || !targetEnvId) return;
    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;
    } catch (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) {
    const { sourceUuid, targetUuid } = event.detail;
    const sDb = sourceDatabases.find((d) => d.uuid === sourceUuid);
    const tDb = targetDatabases.find((d) => d.uuid === targetUuid);

    if (!sDb || !tDb) return;

    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,
      ];
    } catch (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) {
    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.

  $: if (
    $selectedTask &&
    $selectedTask.status === "AWAITING_INPUT" &&
    $selectedTask.input_request
  ) {
    const req = $selectedTask.input_request;
    if (req.type === "database_password") {
      passwordPromptDatabases = req.databases || [];
      passwordPromptErrorMessage = req.error_message || "";
      showPasswordPrompt = true;
    }
  } else if (!$selectedTask || $selectedTask.status !== "AWAITING_INPUT") {
    // Close prompt if task is no longer waiting (e.g. resumed)
    // But only if we are viewing this task.
    // showPasswordPrompt = false;
    // Actually, don't auto-close, let the user or success handler close it.
  }
  // [/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) {
    if (!$selectedTask) return;

    const { passwords } = event.detail;
    try {
      await resumeTask($selectedTask.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 Starts the migration process.
   * @pre     sourceEnvId and targetEnvId must be set and different.
   * @post    Migration task is started and selectedTask is updated.
   */
  async function startMigration() {
    if (!sourceEnvId || !targetEnvId) {
      error =
        $t.migration?.select_both_envs ||
        "Please select both source and target environments.";
      return;
    }
    if (sourceEnvId === targetEnvId) {
      error =
        $t.migration?.different_envs ||
        "Source and target environments must be different.";
      return;
    }
    if (selectedDashboardIds.length === 0) {
      error =
        $t.migration?.select_dashboards ||
        "Please select at least one dashboard to migrate.";
      return;
    }

    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,
      };
      console.log(
        `[MigrationDashboard][Action] Starting migration with selection:`,
        selection,
      );
      const result = await api.postApi("/migration/execute", selection);
      console.log(
        `[MigrationDashboard][Action] Migration started: ${result.task_id} - ${result.message}`,
      );

      // 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);
      } catch (fetchErr) {
        // Fallback: create a temporary task object to switch view immediately
        console.warn(
          $t.migration?.task_placeholder_warn ||
            "Could not fetch task details immediately, using placeholder.",
        );
        selectedTask.set({
          id: result.task_id,
          plugin_id: "superset-migration",
          status: "RUNNING",
          logs: [],
          params: {},
        });
      }
    } catch (e) {
      console.error(`[MigrationDashboard][Failure] Migration failed:`, e);
      error = e.message;
    }
  }
  // [/DEF:startMigration:Function]

  // [DEF:startDryRun:Function]
  /**
   * @purpose Builds pre-flight diff and risk summary without applying migration.
   * @pre     source/target environments and selected dashboards are valid.
   * @post    dryRunResult is populated with backend response.
   * @UX_STATE: Idle -> Dry Run button is enabled when selection is valid.
   * @UX_STATE: Loading -> Dry Run button shows "Dry Run..." and stays disabled.
   * @UX_STATE: Error -> error banner is displayed and dryRunResult resets to null.
   * @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() {
    if (!sourceEnvId || !targetEnvId) {
      error =
        $t.migration?.select_both_envs ||
        "Please select both source and target environments.";
      return;
    }
    if (sourceEnvId === targetEnvId) {
      error =
        $t.migration?.different_envs ||
        "Source and target environments must be different.";
      return;
    }
    if (selectedDashboardIds.length === 0) {
      error =
        $t.migration?.select_dashboards ||
        "Please select at least one dashboard to migrate.";
      return;
    }

    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);
    } catch (e) {
      error = e.message;
      dryRunResult = null;
    } finally {
      dryRunLoading = false;
    }
  }
  // [/DEF:startDryRun:Function]
</script>

<!-- [SECTION: TEMPLATE] -->
<div class="max-w-4xl mx-auto p-6">
  <PageHeader title={$t.nav.migration} />

  <TaskHistory on:viewLogs={handleViewLogs} />

  {#if $selectedTask}
    <div class="mt-6">
      <TaskRunner />
      <div class="mt-4">
        <Button variant="secondary" on:click={() => selectedTask.set(null)}>
          {$t.common.cancel}
        </Button>
      </div>
    </div>
  {:else}
    {#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}

    <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: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] -->

    <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}
          on:change={() => {
            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>

    {#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}
            on:update={handleMappingUpdate}
          />
        {:else if sourceEnvId && targetEnvId}
          <button
            on:click={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"
        on:click={startDryRun}
        disabled={!sourceEnvId ||
          !targetEnvId ||
          sourceEnvId === targetEnvId ||
          selectedDashboardIds.length === 0 ||
          dryRunLoading}
      >
        {dryRunLoading ? "Dry Run..." : "Dry Run"}
      </Button>

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

    {#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}
  {/if}
</div>

<!-- Modals -->
<TaskLogViewer
  bind:show={showLogViewer}
  taskId={logViewerTaskId}
  taskStatus={logViewerTaskStatus}
  on:close={() => (showLogViewer = false)}
/>

<PasswordPrompt
  bind:show={showPasswordPrompt}
  databases={passwordPromptDatabases}
  errorMessage={passwordPromptErrorMessage}
  on:resume={handleResumeMigration}
  on:cancel={() => (showPasswordPrompt = false)}
/>

<!-- [/SECTION] -->

<!-- [/DEF:MigrationDashboard:Component] -->
