<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from "vue";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { Upload, FileText, Database, CheckCircle2, AlertCircle, Loader2, SkipForward, Globe, Terminal, RefreshCw } from "lucide-vue-next";
import { supabase } from "@/integrations/supabase/client";
// TODO(port): dep `fflate` (unzipSync) is not installed in website-vue. ZIP URL imports
// below throw a clear error at runtime until the dependency is added. CSV/TSV imports and
// the "Mode autonome" server-side path are unaffected.
// import { unzipSync } from "fflate";
import {
  CHUNK_SIZE, UI_UPDATE_INTERVAL, CHECKPOINT_INTERVAL, GC_PAUSE_INTERVAL,
  type ImportState, initialState, type Checkpoint,
  saveCheckpoint, loadCheckpoint, clearCheckpoint, normalizeHeader, FIBER_HEADER_MAP, parseLine,
} from "./helpers";

const props = defineProps<{
  type: "addresses" | "fiber";
  label: string;
  description: string;
}>();

const state = ref<ImportState>({ ...initialState });
const urlInput = ref("https://diffusion.mern.gouv.qc.ca/diffusion/RGQ/Vectoriel/Theme/Local/RQA/CSV/RQA_CSV.zip");
const urlMode = ref(false);
const skipRowsInput = ref("");
const importLogs = ref<string[]>([]);
const showLogs = ref(false);
const pendingJob = ref<any>(null);
let abortRef = false;
const fileInputRef = ref<HTMLInputElement | null>(null);
let startTimeRef = 0;
const logEndRef = ref<HTMLDivElement | null>(null);
let autoResumeTriggered = false;

let pendingFileRef: File | null = null;

// Check for existing checkpoint on mount
const checkpoint = ref<Checkpoint | null>(loadCheckpoint(props.type));

const addLog = (msg: string) => {
  importLogs.value = [...importLogs.value, msg];
  setTimeout(() => logEndRef.value?.scrollIntoView({ behavior: "smooth" }), 50);
};

const reset = (keepCheckpoint = false) => {
  abortRef = true;
  if (!keepCheckpoint) clearCheckpoint(props.type);
  pendingFileRef = null;
  pendingJob.value = null;
  urlMode.value = false;
  importLogs.value = [];
  state.value = { ...initialState };
};

// Helper to update job in DB
const updateJob = async (jobId: string, updates: Record<string, any>) => {
  try {
    await supabase.from("import_jobs").update({ ...updates, updated_at: new Date().toISOString() }).eq("id", jobId);
  } catch {}
};

// Check for incomplete DB jobs on mount (URL imports only)
onMounted(() => {
  if (autoResumeTriggered) return;
  autoResumeTriggered = true;

  const checkPendingJobs = async () => {
    const { data: session } = await supabase.auth.getSession();
    if (!session?.session?.user) return;

    const { data: jobs } = await supabase
      .from("import_jobs")
      .select("*")
      .eq("type", props.type)
      .in("status", ["running", "paused"])
      .order("updated_at", { ascending: false })
      .limit(1);

    if (jobs && jobs.length > 0) {
      const job = jobs[0];
      pendingJob.value = job;
      // Restore logs from DB
      if (job.logs && job.logs.length > 0) {
        importLogs.value = job.logs;
        showLogs.value = true;
      }
    }
  };
  checkPendingJobs();
});

// URL-based import: loops calling edge function with DB checkpointing
const processUrl = async (url: string, resumeJobId?: string, resumeSkip?: number, resumeInserted?: number) => {
  abortRef = false;
  startTimeRef = Date.now();
  if (!resumeJobId) {
    importLogs.value = [];
  }
  showLogs.value = true;
  pendingJob.value = null;
  addLog(`🚀 Démarrage import ${props.type} depuis URL${resumeSkip ? ` (reprise à ${resumeSkip} lignes)` : ""}...`);

  // Get user id
  const { data: sessionData } = await supabase.auth.getSession();
  const userId = sessionData?.session?.user?.id;
  if (!userId) {
    addLog("❌ Non authentifié");
    state.value = { ...state.value, status: "error", errorMessages: ["Non authentifié"] };
    return;
  }

  // Create or reuse DB job
  let jobId = resumeJobId;
  if (!jobId) {
    const { data: newJob, error: jobErr } = await supabase
      .from("import_jobs")
      .insert({ type: props.type, source_url: url, status: "running", user_id: userId })
      .select("id")
      .single();
    if (jobErr || !newJob) {
      addLog(`❌ Erreur création job: ${jobErr?.message}`);
      state.value = { ...state.value, status: "error", errorMessages: [jobErr?.message || "Erreur"] };
      return;
    }
    jobId = newJob.id;
  } else {
    await updateJob(jobId, { status: "running" });
  }

  state.value = {
    ...initialState,
    status: "uploading",
    fileName: url.split("/").pop() || "URL import",
    totalRows: 0,
    jobId,
    processedRows: resumeSkip || 0,
    insertedRows: resumeInserted || 0,
  };

  const isZip = url.toLowerCase().includes(".zip");
  let csvText: string | null = null;

  if (isZip) {
    // Download and decompress ZIP in the browser
    addLog(`📥 Téléchargement du fichier ZIP...`);
    try {
      const resp = await fetch(url);
      if (!resp.ok) {
        addLog(`❌ Erreur téléchargement: ${resp.status} ${resp.statusText}`);
        await updateJob(jobId!, { status: "error", error_messages: [`Erreur: ${resp.status}`] });
        state.value = { ...state.value, status: "error", errorMessages: [`Erreur: ${resp.status}`] };
        return;
      }
      const zipData = new Uint8Array(await resp.arrayBuffer());
      addLog(`📦 ZIP téléchargé (${(zipData.length / 1024 / 1024).toFixed(1)} MB), décompression...`);

      // TODO(port): fflate.unzipSync is unavailable. Restore the import above and replace this throw.
      throw new Error("Décompression ZIP indisponible (dépendance fflate manquante).");
      // const files = unzipSync(zipData);
      // const csvEntry = Object.entries(files).find(([name]) =>
      //   name.toLowerCase().endsWith(".csv") || name.toLowerCase().endsWith(".tsv") || name.toLowerCase().endsWith(".txt")
      // );
      // if (!csvEntry) {
      //   addLog(`❌ Aucun fichier CSV trouvé dans le ZIP`);
      //   await updateJob(jobId!, { status: "error", error_messages: ["Aucun CSV dans le ZIP"] });
      //   state.value = { ...state.value, status: "error", errorMessages: ["Aucun CSV dans le ZIP"] };
      //   return;
      // }
      // addLog(`📄 Fichier extrait: ${csvEntry[0]} (${(csvEntry[1].length / 1024 / 1024).toFixed(1)} MB)`);
      // csvText = new TextDecoder("utf-8").decode(csvEntry[1]);
    } catch (err: any) {
      addLog(`💥 Erreur décompression: ${err.message}`);
      await updateJob(jobId!, { status: "error", error_messages: [err.message] });
      state.value = { ...state.value, status: "error", errorMessages: [err.message] };
      return;
    }
  }

  // Now send CSV data in chunks to edge function
  let skipRows = resumeSkip || 0;
  let totalInserted = resumeInserted || 0;
  let totalErrors = 0;
  const allErrorMessages: string[] = [];
  const CSV_CHUNK_SIZE = 5 * 1024 * 1024; // 5 MB
  let chunkCount = 0;
  const DB_SAVE_INTERVAL = 3; // save to DB every N chunks

  while (!abortRef) {
    try {
      // Refresh session before every chunk to prevent expiry
      await supabase.auth.getSession();

      let body: any;
      if (csvText) {
        const chunk = csvText.slice(0, CSV_CHUNK_SIZE);
        body = { csv_text: chunk, type: props.type, skip_rows: skipRows };
      } else {
        body = { url, type: props.type, skip_rows: skipRows };
      }

      const { data, error } = await supabase.functions.invoke("import-csv-url", { body });

      if (error) {
        addLog(`❌ Erreur: ${error.message}`);
        await updateJob(jobId!, { status: "error", error_messages: [error.message], skip_rows: skipRows, total_inserted: totalInserted });
        state.value = { ...state.value, status: "error", errorMessages: [error.message] };
        return;
      }

      if (data) {
        if (data.logs && Array.isArray(data.logs)) {
          data.logs.forEach((l: string) => addLog(l));
        }

        totalInserted += data.inserted || 0;
        totalErrors += data.errors || 0;
        skipRows = data.next_skip || skipRows;
        if (data.errorMessages) {
          allErrorMessages.push(...data.errorMessages);
        }
        chunkCount++;

        addLog(`📊 Chunk ${chunkCount} terminé: +${data.processed || 0} lignes, total inséré: ${totalInserted}, erreurs: ${totalErrors}`);

        state.value = {
          ...state.value,
          processedRows: skipRows,
          insertedRows: totalInserted,
          errorRows: totalErrors,
          totalRows: data.done ? skipRows : Math.max(state.value.totalRows, skipRows + 50000),
          errorMessages: [...new Set(allErrorMessages)].slice(0, 5),
        };

        // Save checkpoint to DB periodically
        if (chunkCount % DB_SAVE_INTERVAL === 0) {
          const recentLogs = importLogs.value.slice(-200); // keep last 200 log lines
          await updateJob(jobId!, {
            skip_rows: skipRows,
            total_inserted: totalInserted,
            total_errors: totalErrors,
            error_messages: [...new Set(allErrorMessages)].slice(0, 20),
            logs: recentLogs,
          });
        }

        if (data.done) {
          addLog(`✅ Import terminé! Total: ${skipRows} lignes, ${totalInserted} insérées, ${totalErrors} erreurs`);
          await updateJob(jobId!, { status: "done", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors });
          state.value = {
            ...state.value,
            status: "done",
            totalRows: skipRows,
            processedRows: skipRows,
          };
          return;
        }

        // For CSV text mode, trim processed text
        if (csvText) {
          const lines = csvText.split("\n");
          const rowsToRemove = data.processed || 0;
          const linesToRemove = skipRows === rowsToRemove ? rowsToRemove + 1 : rowsToRemove;
          csvText = lines.slice(linesToRemove).join("\n");
          skipRows = 0;
          if (!csvText.trim()) {
            addLog(`✅ Import terminé! Total: ${totalInserted} insérées, ${totalErrors} erreurs`);
            await updateJob(jobId!, { status: "done", total_inserted: totalInserted, total_errors: totalErrors });
            state.value = { ...state.value, status: "done" };
            return;
          }
        }

        await new Promise((r) => setTimeout(r, 500));
      }
    } catch (err: any) {
      addLog(`💥 Exception: ${err.message}`);
      // Save progress before dying so we can resume
      await updateJob(jobId!, { status: "paused", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors, error_messages: [err.message] });
      state.value = { ...state.value, status: "error", errorMessages: [`${err.message} — L'import peut être repris automatiquement.`] };
      return;
    }
  }

  // User cancelled — save progress
  if (abortRef && jobId) {
    await updateJob(jobId, { status: "paused", skip_rows: skipRows, total_inserted: totalInserted, total_errors: totalErrors });
  }
};

const processFile = async (file: File, skipRows = 0) => {
  abortRef = false;
  startTimeRef = Date.now();
  const alreadyInserted = skipRows > 0 ? (loadCheckpoint(props.type)?.insertedRows || 0) : 0;
  state.value = {
    ...initialState,
    status: "parsing",
    fileName: file.name,
    resumeFrom: skipRows,
  };

  // Estimate total rows from file size (rough: ~300 bytes per row for addresses CSV)
  const estimatedRows = Math.round(file.size / 300);
  state.value = { ...state.value, totalRows: estimatedRows, status: "uploading" };

  const stream = file.stream();
  const reader = stream.getReader();
  const decoder = new TextDecoder("utf-8", { fatal: false });

  let headers: string[] = [];
  let delimiter = ",";
  let leftover = "";
  let isFirstChunk = true;
  let processedRows = 0;
  let insertedRows = alreadyInserted;
  let errorRows = 0;
  const errorMessages: string[] = [];
  let pendingRows: Record<string, string>[] = [];
  let totalRowsRead = 0; // total rows read from file (including skipped)

  const flushBatch = async (rows: Record<string, string>[]) => {
    if (rows.length === 0 || abortRef) return;
    try {
      // Refresh session token before each batch to prevent expiry during long imports
      await supabase.auth.getSession();
      const { data, error } = await supabase.functions.invoke("import-csv", {
        body: { type: props.type, rows },
      });
      if (error) {
        errorRows += rows.length;
        if (errorMessages.length < 5) errorMessages.push(error.message);
      } else if (data) {
        insertedRows += data.inserted || 0;
        errorRows += data.errors || 0;
        if (data.errorMessages) {
          errorMessages.push(...data.errorMessages.slice(0, 3));
        }
      }
    } catch (err: any) {
      errorRows += rows.length;
      if (errorMessages.length < 5) errorMessages.push(err.message);
    }
    // Throttle UI updates to reduce memory pressure from re-renders
    if (processedRows % UI_UPDATE_INTERVAL < CHUNK_SIZE) {
      state.value = {
        ...state.value,
        processedRows,
        insertedRows,
        errorRows,
        totalRows: Math.max(state.value.totalRows, processedRows + skipRows),
        errorMessages: [...new Set(errorMessages)].slice(0, 5),
      };
    }
    // Save checkpoint less frequently
    if (processedRows % CHECKPOINT_INTERVAL < CHUNK_SIZE) {
      saveCheckpoint(props.type, {
        fileName: file.name,
        fileSize: file.size,
        processedRows: processedRows + skipRows,
        insertedRows,
        timestamp: Date.now(),
      });
    }
  };

  try {
    while (true) {
      if (abortRef) break;
      const { done, value } = await reader.read();
      const chunk = decoder.decode(value, { stream: !done });
      const text = leftover + chunk;
      const lines = text.split(/\r?\n/);

      // Keep last incomplete line for next iteration
      leftover = done ? "" : (lines.pop() || "");

      for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed) continue;

        if (isFirstChunk && headers.length === 0) {
          // Remove BOM
          const cleanLine = trimmed.charCodeAt(0) === 0xfeff ? trimmed.slice(1) : trimmed;
          // Detect delimiter
          const tabCount = (cleanLine.match(/\t/g) || []).length;
          const semiCount = (cleanLine.match(/;/g) || []).length;
          const commaCount = (cleanLine.match(/,/g) || []).length;
          delimiter = tabCount >= semiCount && tabCount >= commaCount ? "\t"
                     : semiCount >= commaCount ? ";" : ",";
          const candidateHeaders = parseLine(cleanLine, delimiter);

          // Skip title lines that have too few columns (e.g. "adresses postales")
          if (candidateHeaders.length < 3) {
            console.log(`[Import] Skipping title line: "${cleanLine}"`);
            continue;
          }

          headers = candidateHeaders;
          console.log(`[Import] Stream - Delimiter: "${delimiter === "\t" ? "TAB" : delimiter}", Headers (${headers.length}):`, headers.join(" | "));

          // For fiber imports, build a mapping from CSV header index to normalized DB column
          if (props.type === "fiber") {
            const headerMapping = headers.map((h) => {
              const norm = normalizeHeader(h);
              const mapped = FIBER_HEADER_MAP[norm];
              return mapped ? `${h} → ${mapped}` : `${h} → ❌ UNMAPPED (${norm})`;
            });
            console.log(`[Import] Fiber header mapping:`, headerMapping.join(" | "));
          }

          isFirstChunk = false;
          continue;
        }

        const values = parseLine(trimmed, delimiter);
        const row: Record<string, string> = {};
        totalRowsRead++;

        // Skip rows that were already processed in a previous run
        if (totalRowsRead <= skipRows) {
          if (totalRowsRead % 100000 === 0) {
            state.value = {
              ...state.value,
              status: "uploading",
              processedRows: 0,
              totalRows: Math.max(state.value.totalRows, estimatedRows),
              errorMessages: [`⏩ Saut de ${totalRowsRead.toLocaleString("fr-CA")} lignes déjà importées...`],
            };
          }
          continue;
        }

        if (props.type === "fiber") {
          // Use normalized mapping for fiber
          headers.forEach((h, idx) => {
            const norm = normalizeHeader(h);
            const dbCol = FIBER_HEADER_MAP[norm];
            if (dbCol && dbCol !== "_skip") {
              row[dbCol] = values[idx] || "";
            }
          });
        } else {
          headers.forEach((h, idx) => {
            row[h] = values[idx] || "";
          });
        }
        pendingRows.push(row);
        processedRows++;

        if (pendingRows.length >= CHUNK_SIZE) {
          await flushBatch(pendingRows);
          pendingRows = [];
          // Periodically pause to let the browser GC reclaim memory
          if (processedRows % GC_PAUSE_INTERVAL < CHUNK_SIZE) {
            await new Promise((r) => setTimeout(r, 50));
          }
        }
      }

      if (done) break;
    }

    // Flush remaining rows
    if (pendingRows.length > 0) {
      await flushBatch(pendingRows);
    }
  } catch (err: any) {
    console.error("[Import] Stream error:", err);
    state.value = {
      ...state.value,
      status: "error",
      errorMessages: [`Erreur de lecture: ${err.message}`],
    };
    return;
  }

  if (!abortRef) {
    clearCheckpoint(props.type); // Import completed successfully
  }
  state.value = {
    ...state.value,
    status: abortRef ? "idle" : "done",
    totalRows: processedRows + skipRows,
    processedRows,
    insertedRows,
    errorRows,
  };
};

const progress = computed(() =>
  state.value.totalRows > 0
    ? Math.round((state.value.processedRows / state.value.totalRows) * 100)
    : 0,
);

const formatNum = (n: number) => n.toLocaleString("fr-CA");

const getStats = () => {
  if (state.value.processedRows === 0 || state.value.status !== "uploading") return { eta: "", speed: "" };
  const elapsed = (Date.now() - startTimeRef) / 1000;
  const rate = state.value.processedRows / elapsed;
  const remaining = state.value.totalRows - state.value.processedRows;
  const etaSec = remaining / rate;
  let eta = "";
  if (etaSec < 60) eta = `~${Math.round(etaSec)}s restant`;
  else if (etaSec < 3600) eta = `~${Math.round(etaSec / 60)} min restant`;
  else {
    const h = Math.floor(etaSec / 3600);
    const m = Math.round((etaSec % 3600) / 60);
    eta = `~${h}h${m.toString().padStart(2, "0")} restant`;
  }
  const speed = rate >= 1000
    ? `${(rate / 1000).toFixed(1)}k lignes/s`
    : `${Math.round(rate)} lignes/s`;
  return { eta, speed };
};

const onFileChange = (e: Event) => {
  const target = e.target as HTMLInputElement;
  const file = target.files?.[0];
  if (file) {
    const cp = loadCheckpoint(props.type);
    if (cp && cp.fileName === file.name && cp.fileSize === file.size) {
      // Same file — offer to resume
      pendingFileRef = file;
      state.value = {
        ...state.value,
        status: "resume_prompt",
        fileName: file.name,
        resumeFrom: cp.processedRows,
        insertedRows: cp.insertedRows,
      };
    } else {
      clearCheckpoint(props.type);
      processFile(file, 0);
    }
  }
  target.value = "";
};

const startAutonomousJob = async () => {
  if (!urlInput.value.trim()) return;
  const { data: sessionData } = await supabase.auth.getSession();
  const userId = sessionData?.session?.user?.id;
  if (!userId) { addLog("❌ Non authentifié"); return; }
  const skipVal = parseInt(skipRowsInput.value || "0") || 0;
  const { data: newJob, error: jobErr } = await supabase
    .from("import_jobs")
    .insert({ type: props.type, source_url: urlInput.value.trim(), status: "queued", user_id: userId, skip_rows: skipVal, total_inserted: skipVal })
    .select("id")
    .single();
  if (jobErr || !newJob) {
    addLog(`❌ Erreur: ${jobErr?.message}`);
    return;
  }
  addLog(`🤖 Job autonome créé (${newJob.id.slice(0, 8)}…). Skip=${skipVal.toLocaleString()} lignes. Le serveur traitera le fichier automatiquement toutes les 2 minutes.`);
  showLogs.value = true;
  pendingJob.value = { ...newJob, type: props.type, source_url: urlInput.value.trim(), status: "queued", skip_rows: skipVal, total_inserted: skipVal };
};

const ignorePendingJob = async () => {
  await supabase.from("import_jobs").update({ status: "done" }).eq("id", pendingJob.value.id);
  pendingJob.value = null;
};

const dismissCheckpoint = () => {
  clearCheckpoint(props.type);
  checkpoint.value = null;
  state.value = { ...initialState };
};

// keep nextTick imported/used consistently (no-op guard)
void nextTick;
</script>

<template>
  <div class="bg-card border border-border rounded-2xl p-6 space-y-4">
    <div class="flex items-start gap-4">
      <div class="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center flex-shrink-0">
        <Database v-if="type === 'addresses'" class="w-6 h-6 text-primary" />
        <FileText v-else class="w-6 h-6 text-primary" />
      </div>
      <div>
        <h3 class="font-semibold text-lg">{{ label }}</h3>
        <p class="text-sm text-muted-foreground">{{ description }}</p>
      </div>
    </div>

    <input
      ref="fileInputRef"
      type="file"
      accept=".csv,.tsv,.txt"
      class="hidden"
      @change="onFileChange"
    />

    <template v-if="state.status === 'idle'">
      <!-- DB-based resume for URL imports -->
      <div v-if="pendingJob" class="bg-accent/50 rounded-lg p-3 text-sm space-y-2 border border-primary/20">
        <div class="flex items-center gap-2">
          <RefreshCw class="w-4 h-4 text-primary" />
          <span class="font-medium">Import URL interrompu détecté</span>
        </div>
        <p class="text-muted-foreground text-xs">
          {{ pendingJob.source_url?.split("/").pop() }} — {{ (pendingJob.skip_rows || 0).toLocaleString("fr-CA") }} lignes traitées
          ({{ (pendingJob.total_inserted || 0).toLocaleString("fr-CA") }} insérées).
          Statut: <span class="font-medium">{{ pendingJob.status }}</span>.
          Dernière mise à jour: {{ new Date(pendingJob.updated_at).toLocaleString("fr-CA") }}.
        </p>
        <div v-if="pendingJob.error_messages && pendingJob.error_messages.length > 0" class="bg-destructive/10 rounded p-2 text-xs text-destructive">
          <span class="font-medium">Raison de l'interruption:</span>
          <ul class="list-disc ml-4 mt-1">
            <li v-for="(msg, i) in pendingJob.error_messages" :key="i">{{ msg }}</li>
          </ul>
        </div>
        <div v-if="pendingJob.skip_rows === 0 && (!pendingJob.error_messages || pendingJob.error_messages.length === 0)" class="bg-yellow-500/10 rounded p-2 text-xs text-yellow-700">
          <span class="font-medium">Cause probable:</span> Le navigateur a manqué de mémoire lors du téléchargement ou de la décompression du ZIP (2.7 Go).
          Essayez depuis un ordinateur de bureau avec plus de RAM, ou utilisez un fichier CSV déjà décompressé.
        </div>
        <details v-if="pendingJob.logs && pendingJob.logs.length > 0" class="text-xs">
          <summary class="cursor-pointer text-muted-foreground hover:text-foreground">Voir les {{ pendingJob.logs.length }} derniers logs</summary>
          <div class="mt-1 max-h-32 overflow-y-auto bg-background/50 rounded p-2 font-mono text-[10px] space-y-0.5">
            <div v-for="(log, i) in pendingJob.logs" :key="i">{{ log }}</div>
          </div>
        </details>
        <div class="flex gap-2">
          <Button size="sm" @click="processUrl(pendingJob.source_url, pendingJob.id, pendingJob.skip_rows || 0, pendingJob.total_inserted || 0)">
            <RefreshCw class="w-3 h-3 mr-1" />
            Reprendre l'import
          </Button>
          <Button variant="ghost" size="sm" @click="ignorePendingJob">
            Ignorer
          </Button>
        </div>
      </div>
      <div v-if="checkpoint" class="bg-accent/50 rounded-lg p-3 text-sm space-y-2">
        <div class="flex items-center gap-2">
          <SkipForward class="w-4 h-4 text-primary" />
          <span class="font-medium">Import fichier interrompu détecté</span>
        </div>
        <p class="text-muted-foreground text-xs">
          {{ checkpoint.fileName }} — {{ checkpoint.processedRows.toLocaleString("fr-CA") }} lignes déjà traitées
          ({{ checkpoint.insertedRows.toLocaleString("fr-CA") }} insérées).
          Sélectionnez le même fichier pour reprendre.
        </p>
        <Button variant="ghost" size="sm" @click="dismissCheckpoint">
          Ignorer et recommencer
        </Button>
      </div>
      <div class="flex gap-2">
        <Button
          @click="fileInputRef?.click()"
          class="flex-1"
          variant="outline"
          size="lg"
        >
          <Upload class="w-5 h-5 mr-2" />
          Fichier CSV
        </Button>
        <Button
          @click="urlMode = !urlMode"
          class="flex-1"
          :variant="urlMode ? 'default' : 'outline'"
          size="lg"
        >
          <Globe class="w-5 h-5 mr-2" />
          Lien URL
        </Button>
      </div>
      <div v-if="urlMode" class="space-y-2">
        <Input
          placeholder="https://exemple.com/fichier.csv ou .zip"
          v-model="urlInput"
        />
        <p class="text-xs text-muted-foreground">
          Supporte les fichiers CSV et ZIP. Le mode autonome décompresse et traite le fichier entièrement côté serveur — idéal depuis un téléphone.
        </p>
        <div class="space-y-2">
          <div class="flex gap-2 items-center">
            <label class="text-xs text-muted-foreground whitespace-nowrap">Lignes à sauter :</label>
            <Input
              type="number"
              :min="0"
              placeholder="0"
              v-model="skipRowsInput"
              class="w-32"
            />
            <span class="text-xs text-muted-foreground">({{ Number(skipRowsInput || 0).toLocaleString() }})</span>
          </div>
          <div class="flex gap-2">
            <Button
              @click="() => { if (urlInput.trim()) processUrl(urlInput.trim()); }"
              :disabled="!urlInput.trim()"
              class="flex-1"
              variant="outline"
            >
              <Globe class="w-4 h-4 mr-2" />
              Import navigateur
            </Button>
            <Button
              @click="startAutonomousJob"
              :disabled="!urlInput.trim()"
              class="flex-1"
            >
              <Database class="w-4 h-4 mr-2" />
              🤖 Mode autonome
            </Button>
          </div>
        </div>
      </div>
    </template>

    <div v-if="state.status === 'resume_prompt'" class="space-y-3">
      <div class="flex items-center gap-2 text-primary">
        <SkipForward class="w-5 h-5" />
        <span class="font-medium">Reprendre l'import ?</span>
      </div>
      <p class="text-sm text-muted-foreground">
        {{ state.fileName }} — {{ formatNum(state.resumeFrom) }} lignes déjà importées
        ({{ formatNum(state.insertedRows) }} insérées). Reprendre à partir de la ligne {{ formatNum(state.resumeFrom + 1) }} ?
      </p>
      <div class="flex gap-2">
        <Button @click="() => { if (pendingFileRef) processFile(pendingFileRef, state.resumeFrom); }">
          <SkipForward class="w-4 h-4 mr-2" />
          Reprendre
        </Button>
        <Button variant="outline" @click="() => { clearCheckpoint(type); if (pendingFileRef) processFile(pendingFileRef, 0); }">
          Recommencer à zéro
        </Button>
        <Button variant="ghost" @click="reset()">
          Annuler
        </Button>
      </div>
    </div>

    <div v-if="state.status === 'parsing' || state.status === 'uploading'" class="space-y-3">
      <div class="flex items-center justify-between text-sm">
        <span class="flex items-center gap-2">
          <Loader2 class="w-4 h-4 animate-spin" />
          {{ state.status === 'parsing' ? 'Lecture du fichier...' :
             state.resumeFrom > 0 ? `Import en cours (reprise après ${formatNum(state.resumeFrom)} lignes)...` : 'Import en cours...' }}
        </span>
        <span class="font-mono text-muted-foreground">
          {{ formatNum(state.processedRows) }} / {{ formatNum(state.totalRows) }}
        </span>
      </div>
      <Progress :value="progress" class="h-3" />
      <div class="flex justify-between text-xs text-muted-foreground">
        <span>{{ progress }}% — {{ getStats().eta }}</span>
        <span>
          ⚡ {{ getStats().speed }} · ✓ {{ formatNum(state.insertedRows) }} insérées · ✗ {{ formatNum(state.errorRows) }} erreurs
        </span>
      </div>
      <Button variant="destructive" size="sm" @click="reset(true)">
        Annuler (garder checkpoint)
      </Button>
    </div>

    <div v-if="state.status === 'done'" class="space-y-3">
      <div class="flex items-center gap-2 text-primary">
        <CheckCircle2 class="w-5 h-5" />
        <span class="font-medium">Import terminé!</span>
      </div>
      <div class="text-sm space-y-1">
        <p>Fichier : {{ state.fileName }}</p>
        <p>Total : {{ formatNum(state.totalRows) }} lignes</p>
        <p>Insérées : {{ formatNum(state.insertedRows) }}</p>
        <p v-if="state.errorRows > 0" class="text-destructive">Erreurs : {{ formatNum(state.errorRows) }}</p>
      </div>
      <div v-if="state.errorMessages.length > 0" class="bg-destructive/10 rounded-lg p-3 text-xs space-y-1">
        <p v-for="(msg, i) in state.errorMessages" :key="i" class="text-destructive">{{ msg }}</p>
      </div>
      <Button variant="outline" @click="reset()">
        Nouvel import
      </Button>
    </div>

    <div v-if="state.status === 'error'" class="space-y-3">
      <div class="flex items-center gap-2 text-destructive">
        <AlertCircle class="w-5 h-5" />
        <span class="font-medium">Erreur</span>
      </div>
      <p v-for="(msg, i) in state.errorMessages" :key="i" class="text-sm text-destructive">{{ msg }}</p>
      <div class="flex gap-2">
        <Button variant="outline" @click="reset(true)">
          Sélectionner le fichier pour reprendre
        </Button>
        <Button variant="ghost" @click="reset()">
          Abandonner
        </Button>
      </div>
    </div>

    <!-- Log Panel -->
    <div v-if="importLogs.length > 0" class="space-y-2">
      <Button
        variant="ghost"
        size="sm"
        @click="showLogs = !showLogs"
        class="flex items-center gap-2 text-xs"
      >
        <Terminal class="w-4 h-4" />
        {{ showLogs ? "Masquer" : "Afficher" }} les logs ({{ importLogs.length }})
      </Button>
      <div v-if="showLogs" class="bg-muted/50 border border-border rounded-lg p-3 max-h-64 overflow-y-auto font-mono text-xs space-y-0.5">
        <div v-for="(line, i) in importLogs" :key="i" :class="line.includes('❌') || line.includes('💥') ? 'text-destructive' : line.includes('✅') ? 'text-primary' : 'text-muted-foreground'">
          {{ line }}
        </div>
        <div ref="logEndRef" />
      </div>
    </div>
  </div>
</template>
