PageSourceSearch

https://www.hamienet.com/assets/DataMigration-D3CXrQtz.js

js hamienet.com collected 2026-09-24 19:00:29 UTC 101,409 bytes, 1,443 lines download raw bytes

1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { a as api } from "./index-D0Bmh-UX.js";
3import ArchiveImporter from "./ArchiveImporter-n0Q0QYy4.js";
4import { d_ as ArrowLeftRight, cY as ChevronLeft, cL as RefreshCw, aY as TriangleAlert, a_ as CircleCheck, b3 as FileHeadphone, bU as Play, bH as Hash, cA as ChevronUp, cm as ArrowRight, b5 as Trash2, dX as Server, b2 as Upload, ch as Database, b9 as Settings, fG as Map$1, cG as Tag, bE as ChevronRight, df as SlidersVertical, cB as ChevronDown } from "./vendor-dVNaLJx_.js";
5import "./vendor-i18n-R-bMoYp_.js";
6import "./synth-BE1TSTD4.js";
7import "./vendor-react-three-MDLA5jog.js";
8import "./wasm-synth-adapter-BrqgBX1y.js";
9import "./vendor-spessasynth-DH8n6o_V.js";
10import "./vendor-audio-DH3w0BzW.js";
11const PRESETS = [
12  { id: "smart_migration", title: "⚡ Smart Migration", description: "One-button catch-up migration for all database tables and files in correct order.", table: "", targetModel: "", database: "" },
13  { id: "custom", title: "Custom Migration", description: "Manually configure source table and target model.", table: "", targetModel: "", database: "" },
14  { id: "users", title: "Preset: Users", description: "Migrate users from `hamienet_OEML.users`. Handles password hash conversion.", table: "users", targetModel: "User", database: "hamienet_O
14EML" },
15  { id: "tracks", title: "Preset: Tracks", description: "Migrate `hamienet_MIDI.Links` table. Maps LinkOwner to User, Note to Artist/Tag.", table: "Links", targetModel: "Midi", database: "hamienet_MIDI" },
16  { id: "comments", title: "Preset: Comments", description: "Migrate `hamienet_MIDI.Reviews` table to Comments. Auto-calculates score.", table: "Reviews", targetModel: "Comment", database: "hamienet_MIDI" },
17  { id: "tags_legacy", title: "Preset: Tags (Explicit)", description: "Migrate `hamienet_MIDI.TagsLink` joined with `Tags`. Connects explicit tags to tracks.", table: "TagsLink", targetModel: "Tag", database: "hamienet_MIDI" },
18  { id: "midi_blobs", title: "Tool: MIDI Blob Extractor", description: "Extract binary MIDI data from legacy `MIDI` table directly to disk.", table: "MIDI", targetModel: "File System", database: "hamienet_MIDI" },
19  { id: "avatar_migration", title: "Tool: Avatar Migration", description: "Migrate legacy avatars from flat folder to structured storage and resolve DB links.", table: "User", targetModel: "Storage", database: "hamienet_O
19EML" },
20  { id: "generate_handles", title: "Tool: Generate Handles", description: "One-time tool to generate unique @handles for all legacy users.", table: "User", targetModel: "User", database: "Prisma" },
21  { id: "archive_importer", title: "Tool: MIDI Archive Importer", description: "Upload a local folder of MIDI files, parse path-based artists/titles, check hashes, and import in resilient chunks.", table: "Local File System", targetModel: "Midi", database: "Local Folder" }
22];
23const USER_PRESET_MAP = {
24  // Screenshot 1
25  id: "user_id",
26  email: "user_email",
27  name: "username",
28  // Mapped to DB column 'name' (Prisma @map("name"))
29  passwordHash: "",
30  // -- Ignore --
31  legacyPasswordHash: "user_password",
32  avatar: "user_avatar",
33  banner: "",
34  // -- Ignore --
35  description: "",
36  // -- Ignore --
37  role: "",
38  // -- Ignore -- (will default to USER/ADMIN based on logic if needed)
39  isBanned: "",
40  // -- Ignore --
41  trustScore: "",
42  // Not in legacy
43  // Screenshot 2
44  createdAt: "user_regdate",
45  updatedAt: "user_lastvisit",
46  canMessage: "",
47  // -- Ignore --
48  credits: "",
49  // -- Ignore --
50  lastCreditReset: "",
51  // -- Ignore --
52  subscriptionTier: "",
53  // -- Ignore --
54  emailVerified: "",
55  // -- Ignore --
56  emailVerificationToken: "",
57  // -- Ignore --
58  website: "user_website",
59  location: "user_from",
60  signature: "user_sig",
61  // Screenshot 3
62  occupation: "user_occ",
63  interests: "user_interests",
64  icq: "user_icq",
65  aim: "user_aim",
66  yim: "user_yim",
67  msnm: "user_msnm",
68  legacyMidiCount: "user_midi",
69  legacyPostCount: "user_posts",
70  timezone: "user_timezone",
71  dateFormat: "user_dateformat",
72  lastVisit: "user_lastvisit",
73  // Screenshot 4
74  donateAmount: "user_donate_amount",
75  donateExpire: "user_donate_expire",
76  language_id: ""
77  // -- Ignore --
78};
79const TRACK_PRESET_MAP = {
80  id: "ID",
81  title: "Title",
82  artist: "Note",
83  // Map Note to Artist as requested
84  description: "Description",
85  userId: "LinkOwner",
86  // Will be resolved to ID on server
87  thumbnail: "poller",
88  // Legacy heuristic
89  views: "Views",
90  plays: "Hits",
91  likesCount: "Rating",
92  // Legacy heuristic
93  midiPath: "url",
94  // The actual file link
95  // Dates
96  createdAt: "Add_Date",
97  updatedAt: "Mod_Date",
98  uploadedAt: "Add_Date",
99  // Same as created
100  // Ignore Status (force default)
101  status: "",
102  // Ignore Relations
103  instruments: "",
104  midiMetadata: ""
105};
106const COMMENT_PRESET_MAP = {
107  id: "ReviewID",
108  midiId: "Review_LinkID",
109  userId: "Review_Owner",
110  // Will be resolved to ID on server
111  text: "Review_Contents",
112  // Server will prepend Subject
113  createdAt: "Review_Date",
114  likes: "Review_WasHelpful",
115  score: "",
116  // Server will calculate (Helpful - NotHelpful)
117  status: "Review_Validated"
118};
119const DataMigration = () => {
120  var _a;
121  const [activePreset, setActivePreset] = reactExports.useState("smart_migration");
122  const [smartMigrationStatus, setSmartMigrationStatus] = reactExports.useState("idle");
123  const [smartMigrationResult, setSmartMigrationResult] = reactExports.useState([]);
124  const [isSidebarOpen, setIsSidebarOpen] = reactExports.useState(true);
125  const [config, setConfig] = reactExports.useState({ sourceTable: "", targetModel: "" });
126  const [sourceMode, setSourceMode] = reactExports.useState("direct");
127  const [dbConfig, setDbConfig] = reactExports.useState({
128    host: "192.168.1.134",
129    user: "root",
130    password: "",
131    database: "hamienet_MIDI",
132    port: 3306
133  });
134  const [fetchLimit, setFetchLimit] = reactExports.useState(0);
135  const [fetchStartId, setFetchStartId] = reactExports.useState(0);
136  const PREVIEW_LIMIT = 50;
137  const [blobStartId, setBlobStartId] = reactExports.useState(0);
138  const [blobLimit, setBlobLimit] = reactExports.useState(50);
139  const [maxExtractedId, setMaxExtractedId] = reactExports.useState(0);
140  const [continueFromLatest, setContinueFromLatest] = reactExports.useState(true);
141  const [remainingCount, setRemainingCount] = reactExports.useState(0);
142  const [isHeaderOpen, setIsHeaderOpen] = reactExports.useState(true);
143  const [isLogsOpen, setIsLogsOpen] = reactExports.useState(true);
144  const [dbSchema, setDbSchema] = reactExports.useState([]);
145  const [availableModels, setAvailableModels] = reactExports.useState([]);
146  const [targetFields, setTargetFields] = reactExports.useState([]);
147  const [fieldMapping, setFieldMapping] = reactExports.useState({});
148  const [sourceFields, setSourceFields] = reactExports.useState([]);
149  const [sourceData, setSourceData] = reactExports.useState([]);
150  const [previewIndex, setPreviewIndex] = reactExports.useState(0);
151  const [previewData, setPreviewData] = reactExports.useState([]);
152  const [isProcessing, setIsProcessing] = reactExports.useState(false);
153  const [isLoadingSchema, setIsLoadingSchema] = reactExports.useState(false);
154  const [logs, setLogs] = reactExports.useState([]);
155  const [successCount, setSuccessCount] = reactExports.useState(0);
156  const [overwrite, setOverwrite] = reactExports.useState(false);
157  const [verbose, setVerbose] = reactExports.useState(false);
158  const [verboseErrorOnly, setVerboseErrorOnly] = reactExports.useState(false);
159  const [resetBeforeMigrating, setResetBeforeMigrating] = reactExports.useState(false);
160  const [catchUpData, setCatchUpData] = reactExports.useState(null);
161  const [isCountingCatchUp, setIsCountingCatchUp] = reactExports.useState(false);
162  reactExports.useEffect(() => {
163    setCatchUpData(null);
164  }, [fetchLimit, fetchStartId, config, dbConfig, fieldMapping, overwrite]);
165  reactExports.useEffect(() => {
166    const loadSchema = async () => {
167      setIsLoadingSchema(true);
168      try {
169        const schema = await api.getDatabaseSchema();
170        setDbSchema(schema);
171        const models = schema.map((t) => t.table);
172        setAvailableModels(models);
173      } catch (e) {
174        console.error("Failed to load schema", e);
175      } finally {
176        setIsLoadingSchema(false);
177      }
178    };
179    loadSchema();
180  }, []);
181  const fetchMidiExtractionStatus = async () => {
182    try {
183      const res = await api.getMidiExtractionStatus(dbConfig);
184      setMaxExtractedId(res.maxExtractedId);
185      setRemainingCount(res.remainingCount);
186    } catch (e) {
187      console.error("Failed to fetch MIDI extraction status", e);
188    }
189  };
190  reactExports.useEffect(() => {
191    fetchMidiExtractionStatus();
192  }, []);
193  reactExports.useEffect(() => {
194    if (activePreset === "midi_blobs") {
195      fetchMidiExtractionStatus();
196    }
197  }, [activePreset, dbConfig]);
198  reactExports.useEffect(() => {
199    if (continueFromLatest) {
200      setBlobStartId(maxExtractedId + 1);
201    }
202  }, [maxExtractedId, continueFromLatest]);
203  reactExports.useEffect(() => {
204    const fetchTargetFields = async () => {
205      if (!config.targetModel) {
206        setTargetFields([]);
207        return;
208      }
209      try {
210        const tableDef = dbSchema.find((t) => t.table === config.targetModel);
211        if (tableDef) {
212          const fields = tableDef.columns.map((c) => c.name);
213          setTargetFields(fields);
214          setFieldMapping({});
215        }
216      } catch (e) {
217        console.error(e);
218      }
219    };
220    fetchTargetFields();
221  }, [config.targetModel, dbSchema]);
222  reactExports.useEffect(() => {
223    const preset = PRESETS.find((p) => p.id === activePreset);
224    if (preset && preset.id !== "custom") {
225      setConfig({
226        sourceTable: preset.table,
227        targetModel: preset.targetModel
228      });
229      if (preset.database) {
230        setDbConfig((prev) => ({ ...prev, database: preset.database }));
231      }
232      setSourceData([]);
233      setPreviewIndex(0);
234      setIsSidebarOpen(false);
235    } else if (preset && preset.id === "custom") {
236      setConfig({ sourceTable: "", targetModel: "" });
237      setSourceData([]);
238    }
239  }, [activePreset]);
240  reactExports.useEffect(() => {
241    if (sourceData.length > 0) {
242      transformData(sourceData);
243    }
244  }, [sourceData, fieldMapping, config.targetModel]);
245  const handleFileUpload = (e) => {
246    var _a2;
247    const file = (_a2 = e.target.files) == null ? void 0 : _a2[0];
248    if (!file) return;
249    const reader = new FileReader();
250    reader.onload = (ev) => {
251      var _a3;
252      try {
253        const text = (_a3 = ev.target) == null ? void 0 : _a3.result;
254        let data = [];
255        if (file.name.endsWith(".json")) {
256          data = JSON.parse(text);
257        } else {
258          alert("SQL parsing not implemented in browser. Please use JSON export script provided.");
259          return;
260        }
261        if (Array.isArray(data)) {
262          setSourceData(data);
263          setPreviewIndex(0);
264          if (data.length > 0) {
265            const keys = Object.keys(data[0]);
266            setSourceFields(keys);
267            autoMapFields(keys, targetFields);
268          }
269        }
270      } catch (err) {
271        alert("Failed to parse file: " + err.message);
272      }
273    };
274    reader.readAsText(file);
275  };
276  const handleDirectFetch = async () => {
277    if (!config.sourceTable) {
278      alert("Please specify a Source Table.");
279      return;
280    }
281    setIsProcessing(true);
282    setLogs((prev) => [...prev, `Connecting to Legacy DB at ${dbConfig.host}... (Fetching preview of ${PREVIEW_LIMIT} records)`]);
283    try {
284      const data = await api.fetchSourceData(dbConfig, config.sourceTable, PREVIEW_LIMIT, config.targetModel);
285      setSourceData(data);
286      setPreviewIndex(0);
287      if (data.length > 0) {
288        const keys = Object.keys(data[0]);
289        setSourceFields(keys);
290        autoMapFields(keys, targetFields);
291        setLogs((prev) => [...prev, `Fetched ${data.length} preview records.`]);
292        setIsHeaderOpen(false);
293      } else {
294        setLogs((prev) => [...prev, `Fetched 0 records.`]);
295      }
296    } catch (e) {
297      console.error("Migration Error:", e);
298      setLogs((prev) => [...prev, `ERROR: ${e.message}`]);
299      alert(`Failed to fetch data: ${e.message}`);
300    } finally {
301      setIsProcessing(false);
302    }
303  };
304  const handleSmartMigration = async () => {
305    setIsProcessing(true);
306    setSmartMigrationStatus("running");
307    setSmartMigrationResult([]);
308    setLogs((prev) => [...prev, `⚡ Starting 1-Button Smart Migration sequence...`]);
309    const runStep = async (stepName, fn) => {
310      const startTime = Date.now();
311      setLogs((prev) => [...prev, `[Smart Migration] Running step: ${stepName}...`]);
312      try {
313        const res = await fn();
314        const durationMs = Date.now() - startTime;
315        setLogs((prev) => [...prev, `✓ [Smart Migration] ${stepName} completed successfully in ${durationMs}ms.`]);
316        const processed = res.processed ?? (res.migratedCount !== void 0 ? res.migratedCount + (res.defaultedCount ?? 0) : res.count ?? 0);
317        const inserted = res.inserted ?? (res.migratedCount !== void 0 ? res.migratedCount : res.processed !== void 0 ? res.processed : res.count ?? 0);
318        const skipped = res.skipped ?? res.conflicts ?? res.defaultedCount ?? 0;
319        const errors = res.errors ?? 0;
320        return {
321          step: stepName,
322          status: "completed",
323          processed,
324          inserted,
325          skipped,
326          errors,
327          durationMs
328        };
329      } catch (err) {
330        const durationMs = Date.now() - startTime;
331        setLogs((prev) => [...prev, `❌ [Smart Migration] ${stepName} failed: ${err.message}`]);
332        return {
333          step: stepName,
334          status: "failed",
335          processed: 0,
336          inserted: 0,
337          skipped: 0,
338          errors: 1,
339          details: err.message,
340          durationMs
341        };
342      }
343    };
344    const stepsResults = [];
345    const usersRes = await runStep(
346      "Users",
347      () =>
347 api.executeMigration({ ...dbConfig, database: "hamienet_OEML" }, "users", "User", USER_PRESET_MAP, 0, 0, false, false)
348    );
349    stepsResults.push(usersRes);
350    setSmartMigrationResult([...stepsResults]);
351    if (usersRes.status === "failed") {
352      setSmartMigrationStatus("error");
353      setIsProcessing(false);
354      return;
355    }
356    const tracksRes = await runStep(
357      "Tracks",
358      () => api.executeMigration({ ...dbConfig, database: "hamienet_MIDI" }, "Links", "Midi", TRACK_PRESET_MAP, 0, 0, false, false)
359    );
360    stepsResults.push(tracksRes);
361    setSmartMigrationResult([...stepsResults]);
362    if (tracksRes.status === "failed") {
363      setSmartMigrationStatus("error");
364      setIsProcessing(false);
365      return;
366    }
367    const commentsRes = await runStep(
368      "Comments",
369      () => api.executeMigration({ ...dbConfig, database: "hamienet_MIDI" }, "Reviews", "Comment", COMMENT_PRESET_MAP, 0, 0, false, false)
370    );
371    stepsResults.push(commentsRes);
372    setSmartMigrationResult([...stepsResults]);
373    if (commentsRes.status === "failed") {
374      setSmartMigrationStatus("error");
375      setIsProcessing(false);
376      return;
377    }
378    const tagsRes = await runStep(
379      "Tags",
380      () => api.executeMigration({ ...dbConfig, database: "hamienet_MIDI" }, "TagsLink", "Tag", {}, 0, 0, false, false)
381    );
382    stepsResults.push(tagsRes);
383    setSmartMigrationResult([...stepsResults]);
384    if (tagsRes.status === "failed") {
385      setSmartMigrationStatus("error");
386      setIsProcessing(false);
387      return;
388    }
389    const blobsRes = await runStep("MIDI Blobs", async () => {
390      const status = await api.getMidiExtractionStatus({ ...dbConfig, database: "hamienet_MIDI" });
391      const extractRes = await api.extractMidiFiles({ ...dbConfig, database: "hamienet_MIDI" }, status.maxExtractedId + 1, 0);
392      return {
393        count: extractRes.count,
394        skipped: 0,
395        errors: 0
396      };
397    });
398    stepsResults.push(blobsRes);
399    setSmartMigrationResult([...stepsResults]);
400    if (blobsRes.status === "failed") {
401      setSmartMigrationStatus("error");
402      setIsProcessing(false);
403      return;
404    }
405    const avatarsRes = await runStep(
406      "Avatar Migration",
407      () => api.executeAvatarMigration()
408    );
409    stepsResults.push(avatarsRes);
410    setSmartMigrationResult([...stepsResults]);
411    if (avatarsRes.status === "failed") {
412      setSmartMigrationStatus("error");
413      setIsProcessing(false);
414      return;
415    }
416    const handlesRes = await runStep(
417      "Handle Generation",
418      () => api.generateUserHandles()
419    );
420    stepsResults.push(handlesRes);
421    setSmartMigrationResult([...stepsResults]);
422    if (handlesRes.status === "failed") {
423      setSmartMigrationStatus("error");
424    } else {
425      setSmartMigrationStatus("done");
426      setLogs((prev) => [...prev, `🎉 Smart Migration Sequence completed successfully!`]);
427    }
428    setIsProcessing(false);
429  };
430  const handleBlobExtraction = async () => {
431    setIsProcessing(true);
432    setLogs((prev) => [...prev, `Starting BLOB extraction from ID ${blobStartId} (Limit: ${blobLimit})...`]);
433    try {
434      const res = await api.extractMidiFiles(dbConfig, blobStartId, blobLimit);
435      setLogs((prev) => [...prev, `✓ ${res.message}`]);
436      if (res.lastId > blobStartId && !continueFromLatest) {
437        setBlobStartId(res.lastId + 1);
438      }
439      setSuccessCount(res.count);
440      await fetchMidiExtractionStatus();
441    } catch (e) {
442      setLogs((prev) => [...prev, `ERROR: ${e.message}`]);
443    } finally {
444      setIsProcessing(false);
445    }
446  };
447  const handleAvatarMigration = async () => {
448    setIsProcessing(true);
449    setLogs((prev) => [...prev, `Starting Legacy Avatar Migration...`]);
450    setSuccessCount(0);
451    try {
452      const res = await api.executeAvatarMigration();
453      setLogs((prev) => [...prev, `✓ ${res.message}`]);
454    } catch (e) {
455      setLogs((prev) => [...prev, `ERROR: ${e.message}`]);
456    } finally {
457      setIsProcessing(false);
458    }
459  };
460  const autoMapFields = (sourceKeys, targets) => {
461    const newMapping = {};
462    let presetMap = {};
463    if (activePreset === "users") presetMap = USER_PRESET_MAP;
464    else if (activePreset === "tracks") presetMap = TRACK_PRESET_MAP;
465    else if (activePreset === "comments") presetMap = COMMENT_PRESET_MAP;
466    targets.forEach((target) => {
467      if (presetMap[target] !== void 0) {
468        const mappedSource = presetMap[target];
469        if (mappedSource === "") {
470          newMapping[target] = "";
471          return;
472        }
473        if (sourceKeys.includes(mappedSource)) {
474          newMapping[target] = mappedSource;
475          return;
476        }
477      }
478      const match = sourceKeys.find(
479        (k) => k.toLowerCase() === target.toLowerCase() || k.toLowerCase().endsWith(`_${target.toLowerCase()}`) || target.toLowerCase().endsWith(`_${k.toLowerCase()}`)
480      );
481      if (match) newMapping[target] = match;
482      if (target === "userId" && sourceKeys.includes("submitter")) newMapping[target] = "submitter";
483      if (target === "name" && sourceKeys.includes("username")) newMapping[target] = "username";
484      if (target === "username" && sourceKeys.includes("name")) newMapping[target] = "name";
485    });
486    setFieldMapping(newMapping);
487  };
488  const updateMapping = (target, source) => {
489    setFieldMapping((prev) => ({
490      ...prev,
491      [target]: source
492    }));
493  };
494  const transformData = (rawData) => {
495    if (!config.targetModel) return;
496    const modelSchema = dbSchema.find((t) => t.table === config.targetModel);
497    if (!modelSchema) return;
498    const colDefMap = /* @__PURE__ */ new Map();
499    if (modelSchema.columns) {
500      for (const col of modelSchema.columns) {
501        colDefMap.set(col.name, col);
502      }
503    }
504    const transformed = rawData.map((item) => {
505      const mappedItem = {};
506      Object.entries(fieldMapping).forEach(([targetField, sourceField]) => {
507        if (sourceField && item[sourceField] !== void 0) {
508          let val = item[sourceField];
509          const colDef = colDefMap.get(targetField);
510          if (colDef) {
511            const type = String(colDef.type).toLowerCase();
512            if (type.includes("int") || type.includes("number") || type === "Int") {
513              const parsed = parseInt(val, 10);
514              val = isNaN(parsed) ? 0 : parsed;
515            } else if (type.includes("float") || type.includes("double") || type.includes("real") || type === "Float") {
516              const parsed = parseFloat(val);
517              val = isNaN(parsed) ? 0 : parsed;
518            } else if (type.includes("bool") || type === "Boolean") {
519              val = val === 1 || val === "1" || val === true || val === "true";
520            } else if (type.includes("char") || type.includes("text") || type === "String") {
521              if (val === null || val === void 0) val = "";
522              else val = String(val);
523            }
524          }
525          if (targetField === "createdAt" || targetField === "updatedAt" || targetField === "lastVisit" || targetField === "lastCreditReset" || targetField === "uploadedAt") {
526            val = new Date(item[sourceField]).toISOString();
527          }
528          if (targetField === "role" && !val) val = "USER";
529          if (targetField === "isBanned" && typeof val === "number") val = val === 1;
530          if (activePreset === "comments") {
531            if (targetField === "text") {
532              let subject = (item["Review_Subject"] || "").trim();
533              const contents = (item["Review_Contents"] || "").trim();
534              if (subject && !/[.!?]$/.test(subject)) {
535                subject += ".";
536              }
537              val = subject ? `${subject}
538
539${contents}` : contents;
540            } else if (targetField === "likes") {
541              const helpful = parseInt(item["Review_WasHelpful"] || "0");
542              const notHelpful = parseInt(item["Review_WasNotHelpful"] || "0");
543              val = helpful - notHelpful;
544            } else if (targetField === "status") {
545              const validated = (item["Review_Validated"] || "yes").toLowerCase();
546              if (validated === "yes") val = "visible";
547              else if (validated === "no") val = "pending";
548              else if (validated === "deleted") val = "hidden";
549              else val = "visible";
550            } else if (targetField === "id" && item["ReviewID"]) {
551              val = parseInt(item["ReviewID"]);
552            }
553          }
554          if (activePreset === "tracks") {
555            if (targetField === "status") val = "completed";
556          }
557          if (activePreset === "users") {
558            if (targetField === "role" && !val) val = "USER";
559            if (targetField === "email" && !val) val = "[email protected]";
560          }
561          mappedItem[targetField] = val;
562        }
563      });
564      return { target: config.targetModel, data: mappedItem };
565    });
566    setPreviewData(transformed);
567  };
568  const handleCalculateCatchUp = async () => {
569    if (!config.sourceTable || !config.targetModel) return;
570    setIsCountingCatchUp(true);
571    setLogs((prev) => [...prev, `Calculating catch-up data rows (Limit: ${fetchLimit === 0 ? "ALL" : fetchLimit}, Start ID: ${fetchStartId})...`]);
572    try {
573      const res = await api.countCatchUp(dbConfig, config.sourceTable, config.targetModel, fieldMapping, fetchLimit, fetchStartId);
574      setCatchUpData(res);
575      setLogs((prev) => [
576        ...prev,
577        `✓ Catch-up calculation complete:`,
578        `  - Total source records matching query: ${res.totalSourceRows}`,
579        `  - Existing records in target DB: ${res.existingRowsCount}`,
580        `  - New records to catch-up (will be added): ${res.newRowsCount}`
581      ]);
582    } catch (e) {
583      setLogs((prev) => [...prev, `ERROR calculating catch-up: ${e.message}`]);
584      alert(`Failed to calculate catch-up: ${e.message}`);
585    } finally {
586      setIsCountingCatchUp(false);
587    }
588  };
589  const handleExecute = async () => {
590    if (!config.sourceTable || !config.targetModel) return;
591    setIsProcessing(true);
592    const limitStr = fetchLimit === 0 ? "ALL" : fetchLimit;
593    setLogs((prev) => [
594      ...prev,
595      `Starting server-side migration (Limit: ${limitStr})...`,
596      `✓ Migrating ${limitStr} records`
597    ]);
598    setSuccessCount(0);
599    try {
600      if (resetBeforeMigrating) {
601        const warningMsg = config.targetModel.toLowerCase() === "user" ? `WARNING: Enabling "Reset Table" for User will delete other accounts but preserve your active Admin session to keep you from being logged out. Do you want to proceed?` : `WARNING: Enabling "Reset Table" will completely clear all records in the '${config.targetModel}' table first. Do you want to proceed?`;
602        if (!confirm(warningMsg)) {
603          setIsProcessing(false);
604          return;
605        }
606        setLogs((prev) => [...prev, `WARNING: Initiating Reset for ${config.targetModel} before migration...`]);
607        await api.resetDatabase(config.targetModel);
608        setLogs((prev) => [...prev, `SUCCESS: ${config.targetModel} data cleared.`]);
609        setResetBeforeMigrating(false);
610      }
611      const sendVerbose = verbose || verboseErrorOnly;
612      const res = await api.executeMigration(dbConfig, config.sourceTable, config.targetModel, fieldMapping, fetchLimit, fetchStartId, overwrite, sendVerbose);
613      setSuccessCount(res.count);
614      if (sendVerbose && res.verboseLogs && res.verboseLogs.length > 0) {
615        setLogs((prev) => [...prev, ...res.verboseLogs]);
616      }
617      setLogs((prev) => [...prev, `✓ Migrated ${res.count} records`]);
618    } catch (e) {
619      setLogs((prev) => [...prev, `ERROR: ${e.message}`]);
620    } finally {
621      setIsProcessing(false);
622    }
623  };
624  const handleResetDatabase = async (target = "all") => {
625    const msg = target === "all" ? "DANGER: This will delete ALL User, Track, and Comment data from the database. Are you sure?" : `WARNING: This will delete ALL records in the '${target}' table. Are you sure?`;
626    if (!confirm(msg)) return;
627    setIsProcessing(true);
628    setLogs((prev) => [...prev, `WARNING: Initiating Reset for ${target}...`]);
629    try {
630      await api.resetDatabase(target);
631      setLogs((prev) => [...prev, `SUCCESS: ${target} data cleared.`]);
632      if (target === "all") alert("Database reset complete. You can now start a fresh migration.");
633    } catch (e) {
634      setLogs((prev) => [...prev, `ERROR: Reset failed - ${e.message}`]);
635      alert("Reset failed. Check server logs.");
636    } finally {
637      setIsProcessing(false);
638    }
639  };
640  const currentRecord = sourceData[previewIndex] || {};
641  const currentTransformed = ((_a = previewData[previewIndex]) == null ? void 0 : _a.data) || {};
642  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-full bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden", children: [
643    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `${isSidebarOpen ? "w-72" : "w-16 items-center"} bg-zinc-50 dark:bg-zinc-950 border-r border-zinc-200 dark:border-zinc-800 flex flex-col shrink-0 transition-all duration-300 shrink-0`, children: [
644      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `p-6 border-b border-zinc-200 dark:border-zinc-800 flex items-center ${isSidebarOpen ? "justify-between w-full" : "justify-center p-4 cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-900"}`, onClick: () => !isSidebarOpen && setIsSidebarOpen(true), children: isSidebarOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
645        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
646          /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
647            /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeftRight, { className: "text-indigo-500" }),
648            " Migration"
649          ] }),
650          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mt-1", children: "Legacy System Import Wizard" })
651        ] }),
652        /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsSidebarOpen(false), className: "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 20 }) })
653      ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { title: "Open Migration Menu", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeftRight, { className: "text-indigo-500 cursor-pointer", size: 24 }) }) }),
654      isSidebarOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto w-full py-4", children: PRESETS.map((preset, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
655        "button",
656        {
657          onClick: () => {
658            setActivePreset(preset.id);
659            setResetBeforeMigrating(false);
660          },
661          className: `w-full text-left px-6 py-4 border-l-4 transition-all ${activePreset === preset.id ? "bg-white dark:bg-zinc-900 border-indigo-500 shadow-sm" : "border-transparent hover:bg-zinc-100 dark:hover:bg-zinc-900 text-zinc-500"}`,
662          children: [
663            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `font-bold text-sm ${activePreset === preset.id ? "text-indigo-600 dark:text-indigo-400" : "text-zinc-700 dark:text-zinc-300"}`, children: preset.title }),
664            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500 mt-1 line-clamp-2", children: preset.description })
665          ]
666        },
667        preset.id
668      )) }),
669      isSidebarOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full p-4 border-t border-zinc-200 dark:border-zinc-800 bg-red-50/50 dark:bg-red-900/10", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
670        "button",
671        {
672          onClick: () => handleResetDatabase("all"),
673          disabled: isProcessing,
674          className: "w-full flex items-center justify-center gap-2 px-4 py-2 bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400 hover:bg-red-200 dark:hover:bg-red-900/50 rounded-lg text-xs font-bold transition-colors disabled:opacity-50",
675          children: [
676            isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 14 }),
677            "Factory Reset (All Data)"
678          ]
679        }
680      ) })
681    ] }),
682    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
683      activePreset === "smart_migration" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col p-8 overflow-y-auto bg-zinc-50 dark:bg-black/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mx-auto w-full bg-white dark:bg-zinc-900 p-8 rounded-2xl shadow-sm border border-zinc-200 dark:border-zinc-800", children: [
684        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-6", children: [
685          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-indigo-100 dark:bg-indigo-900/30 rounded-xl flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold text-xl", children: "⚡" }),
686          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
687            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Smart Migration Sequence" }),
688            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Run all migration steps in correct order with a single click. Skips existing, copies files, and maps legacy data cleanly." })
689          ] })
690        ] }),
691        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
692          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800", children: [
693            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-zinc-500 uppercase mb-3", children: "Target Configuration" }),
694            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
695              /* @__P
695URE__ */ jsxRuntimeExports.jsxs("div", { children: [
696                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Source MySQL Host" }),
697                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { disabled: true, value: dbConfig.host, className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono cursor-not-allowed" })
698              ] }),
699              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
700                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Port" }),
701                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { disabled: true, value: dbConfig.port, className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono cursor-not-allowed" })
702              ] }),
703              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
704                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "User" }),
705                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { disabled: true, value: dbConfig.user, className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono cursor-not-allowed" })
706              ] }),
707              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
708                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Password" }),
709                /* @__PURE__ */ jsxRuntimeExports.jsx(
710                  "input",
711                  {
712                    type: "password",
713                    value: dbConfig.password,
714                    onChange: (e) => setDbConfig({ ...dbConfig, password: e.target.value }),
715                    placeholder: "(None required if run inside network)",
716                    className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 text-zinc-900 dark:text-white"
717                  }
718                )
719              ] })
720            ] })
721          ] }),
722          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden", children: [
723            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 px-4 py-3 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center", children: [
724              /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-zinc-700 dark:text-zinc-300 uppercase", children: "Sequence Progress" }),
725              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs px-2 py-0.5 rounded-full font-bold uppercase ${smartMigrationStatus === "running" ? "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400 animate-pulse" : smartMigrationStatus === "done" ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" : smartMigrationStatus === "error" ? "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400" : "bg-zinc-200 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400"}`, children: smartMigrationStatus === "running" ? "Running" : smartMigrationStatus === "done" ? "Completed" : smartMigrationStatus === "error" ? "Failed" : "Idle" })
726            ] }),
727            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "divide-y divide-zinc-200 dark:divide-zinc-800", children: [
728              { name: "Users", desc: "Migrate users from hamienet_OEML.users" },
729              { name: "Tracks", desc: "Migrate tracks from hamienet_MIDI.Links (resolves owners)" },
730              { name: "Comments", desc: "Migrate review posts to Comments" },
731              { name: "Tags", desc: "Migrate explicit tags from TagsLink joined with Tags" },
732              { name: "MIDI Blobs", desc: "Extract MIDI binaries from MIDI table to local disk (no limit)" },
733              { name: "Avatar Migration", desc: "Copy avatars from TBA directory and update user records" },
734              { name: "Handle Generation", desc: "Generate unique user @handles" }
735            ].map((stepItem, idx) => {
736              const stepResult = smartMigrationResult.find((r) => r.step === stepItem.name);
737              return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex items-center justify-between hover:bg-zinc-50/50 dark:hover:bg-zinc-900/50", children: [
738                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
739                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm text-zinc-900 dark:text-white flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
740                    idx + 1,
741                    ". ",
742                    stepItem.name
743                  ] }) }),
744                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-500", children: stepItem.desc })
745                ] }),
746                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3", children: stepResult ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-right", children: [
747                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold px-2 py-0.5 rounded ${stepResult.status === "completed" ? "bg-green-50 text-green-700 dark:bg-green-900/10 dark:text-green-400" : "bg-red-50 text-red-700 dark:bg-red-900/10 dark:text-red-400"}`, children: stepResult.status === "completed" ? "Success" : "Failed" }),
748                  stepResult.status === "completed" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] text-zinc-400 mt-1", children: [
749                    "Processed: ",
750                    stepResult.processed,
751                    " | Inserted: ",
752                    stepResult.inserted,
753                    " | Skipped/Conflicts: ",
754                    stepResult.skipped,
755                    " | Errors: ",
756                    stepResult.errors,
757                    " (",
758                    stepResult.durationMs,
759                    "ms)"
760                  ] }),
761                  stepResult.details && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-indigo-500 dark:text-indigo-400 mt-0.5 italic", children: stepResult.details })
762                ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 font-medium", children: "Pending" }) })
763              ] }, idx);
764            }) })
765          ] }),
766          smartMigrationStatus === "done" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 bg-green-50/50 dark:bg-green-950/10 border border-green-200 dark:border-green-800/40 rounded-xl space-y-4", children: [
767            /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-black text-green-900 dark:text-green-400 text-md flex items-center gap-2", children: [
768              /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 20 }),
769              "Smart Migration Summary"
770            ] }),
771            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-xs", children: [
772              /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-green-200 dark:border-green-800 text-green-800 dark:text-green-300 font-bold", children: [
773                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Step" }),
774                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Processed" }),
775                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Inserted (New)" }),
776                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Skipped / Conflicts" }),
777                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Errors" }),
778                /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-2", children: "Duration" })
779              ] }) }),
780              /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-green-100 dark:divide-green-900/40 text-zinc-700 dark:text-zinc-300", children: smartMigrationResult.map((r, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "py-2", children: [
781                /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 font-semibold", children: r.step }),
782                /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 font-mono", children: r.processed }),
783                /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 font-mono text-emerald-600 dark:text-emerald-400 font-bold", children: [
784                  "+",
785                  r.inserted
786                ] }),
787                /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 font-mono", children: r.skipped }),
788                /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-2 font-mono text-red-500", children: r.errors }),
789                /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-2 font-mono", children: [
790                  r.durationMs,
791                  "ms"
792                ] })
793              ] }, i)) })
794            ] }) })
795          ] }),
796          /* @__PURE__ */ jsxRuntimeExports.jsxs(
797            "button",
798            {
799              onClick: handleSmartMigration,
800              disabled: isProcessing,
801              className: "w-full py-4 bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700 text-white font-black text-md rounded-xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50",
802              children: [
803                isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 22 }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "⚡" }),
804                isProcessing ? "Executing Smart Migration Sequence..." : "⚡ Run Smart Migration"
805              ]
806            }
807          )
808        ] })
809      ] }) }) : activePreset === "midi_blobs" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col p-8 overflow-y-auto bg-zinc-50 dark:bg-black/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-2xl mx-auto w-full bg-white dark:bg-zinc-900 p-8 rounded-2xl shadow-sm border border-zinc-200 dark:border-zinc-800", children: [
810        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-6", children: [
811          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-indigo-100 dark:bg-indigo-900/30 rounded-xl flex items-center justify-center text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(FileHeadphone, { size: 24 }) }),
812          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
813            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "MIDI Blob Extractor" }),
814            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Extract binary data from legacy SQL table directly to disk." })
815          ] })
816        ] }),
817        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
818          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800", children: [
819            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-zinc-500 uppercase mb-3", children: "Target Configuration" }),
820            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
821              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
822                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Source Table" }),
823                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { disabled: true, value: "MIDI", className: "w-full bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm text-zinc-500 font-mono cursor-not-allowed" })
824              ] }),
825              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
826                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Target Path" }),
827                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { disabled: true, value: "/public/storage/tracks/midi/{group}/{id}.mid", className: "w-full bg-zinc-200 dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm text-zinc-500 font-mono cursor-not-allowed" })
828              ] })
829            ] })
830          ] }),
831          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
832            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800 text-center flex flex-col justify-center min-h-[96px]", children: [
833              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 uppercase block mb-1", children: "Latest Extracted ID" }),
834              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl font-black text-zinc-900 dark:text-white font-mono", children: maxExtractedId })
835            ] }),
836            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800 text-center flex flex-col justify-center min-h-[96px]", children: [
837              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 uppercase block mb-1", children: "Next ID to Extract" }),
838              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl font-black text-indigo-600 dark:text-indigo-400 font-mono", children: maxExtractedId + 1 }),
839              remainingCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-500 mt-1 font-medium", children: [
840                "(",
841                remainingCount,
842                " remaining in DB)"
843              ] })
844            ] })
845          ] }),
846          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800 flex items-center justify-between", children: [
847            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col pr-4", children: [
848              /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-zinc-900 dark:text-white", children: "Auto-continue from latest" }),
849              /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-500 mt-0.5", children: [
850                "Start extraction automatically from the latest unextracted MIDI ID on disk (",
851                maxExtractedId + 1,
852                ")"
853              ] })
854            ] }),
855            /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "relative inline-flex items-center cursor-pointer shrink-0", children: [
856              /* @__PURE__ */ jsxRuntimeExports.jsx(
857                "input",
858                {
859                  type: "checkbox",
860                  checked: continueFromLatest,
861                  onChange: (e) => setContinueFromLatest(e.target.checked),
862                  className: "sr-only peer"
863                }
864              ),
865              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-11 h-6 bg-zinc-200 peer-focus:outline-none dark:bg-zinc-700 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-zinc-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-zinc-600 peer-checked:bg-indigo-600" })
866            ] })
867          ] }),
868          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
869            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
870              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Start ID" }),
871              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
872                /* @__PURE__ */ jsxRuntimeExports.jsx(
873                  "input",
874                  {
875                    type: "number",
876                    value: blobStartId,
877                    disabled: continueFromLatest,
878                    onChange: (e) => setBlobStartId(parseInt(e.target.value) || 0),
879                    className: `w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 text-zinc-900 dark:text-white ${continueFromLatest ? "opacity-60 cursor-not-allowed bg-zinc-50 dark:bg-zinc-900" : ""}`
880                  }
881                ),
882                continueFromLatest && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute right-2 top-2 px-1.5 py-0.5 bg-indigo-50 dark:bg-indigo-900/40 border border-indigo-200 dark:border-indigo-800 text-[10px] text-indigo-600 dark:text-indigo-400 font-bold rounded-md", children: "Locked" })
883              ] }),
884              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mt-1", children: "Starting row ID to process." })
885            ] }),
886            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
887              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: "Batch Size (Limit)" }),
888              /* @__PURE__ */ jsxRuntimeExports.jsx(
889                "input",
890                {
891                  type: "number",
892                  value: blobLimit,
893                  onChange: (e) => setBlobLimit(parseInt(e.target.value) || 0),
894                  className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 text-zinc-900 dark:text-white"
895                }
896              ),
897              /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mt-1", children: "Rows to fetch per execution." })
898            ] })
899          ] }),
900          /* @__PURE__ */ jsxRuntimeExports.jsxs(
901            "button",
902            {
903              onClick: handleBlobExtraction,
904              disabled: isProcessing,
905              className: "w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50",
906              children: [
907                isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 20 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 20 }),
908                isProcessing ? "Extracting Blobs..." : "Execute Batch Extraction"
909              ]
910            }
911          )
912        ] })
913      ] }) }) : activePreset === "avatar_migration" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col p-8 overflow-y-auto bg-zinc-50 dark:bg-black/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-2xl mx-auto w-full bg-white dark:bg-zinc-900 p-8 rounded-2xl shadow-sm border border-zinc-200 dark:border-zinc-800", children: [
914        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-6", children: [
915          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-indigo-100 dark:bg-indigo-900/30 rounded-xl flex items-center justify-center text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeftRight, { size: 24 }) }),
916          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
917            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Legacy Avatar Migration" }),
918            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Migrate legacy filename-based avatars to structured ID-based storage." })
919          ] })
920        ] }),
921        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
922          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800", children: [
923            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-zinc-500 uppercase mb-3", children: "Process Overview" }),
924            /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "text-sm text-zinc-600 dark:text-zinc-400 list-disc list-inside space-y-2", children: [
925              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
926                "Scans ",
927                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "/storage/avatars/TBA/" }),
928                " for uploaded legacy user images."
929              ] }),
930              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
931                "Matches filenames to the ",
932                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "avatar" }),
933                " field in the Prisma ",
934                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "User" }),
935                " table."
936              ] }),
937              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
938                "Moves matched files to ",
939                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "/storage/avatars/<group>/<id>.jpg" }),
940                "."
941              ] }),
942              /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Assigns a random default image to users without a custom legacy avatar." }),
943              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
944                "Updates the Database automatically mapping ",
945                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: '"custom"' }),
946                " or ",
947                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: '"default:<filename>"' }),
948                "."
949              ] })
950            ] }),
951            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 p-3 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-800/50 rounded-lg flex gap-3 text-amber-700 dark:text-amber-400", children: [
952              /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 18, className: "shrink-0" }),
953              /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs", children: [
954                "Ensure you have manually copied the legacy avatar flat files into the ",
955                /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "TBA" }),
956                " folder on your storage server before proceeding. Please also seed the default collection first."
957              ] })
958            ] })
959          ] }),
960          /* @__PURE__ */ jsxRuntimeExports.jsxs(
961            "button",
962            {
963              onClick: handleAvatarMigration,
964              disabled: isProcessing,
965              className: "w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50",
966              children: [
967                isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 20 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 20 }),
968                isProcessing ? "Migrating Avatars..." : "Execute Full Avatar Migration"
969              ]
970            }
971          )
972        ] })
973      ] }) }) : activePreset === "generate_handles" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col p-8 overflow-y-auto bg-zinc-50 dark:bg-black/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-2xl mx-auto w-full bg-white dark:bg-zinc-900 p-8 rounded-2xl shadow-sm border border-zinc-200 dark:border-zinc-800", children: [
974        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 mb-6", children: [
975          /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-indigo-100 dark:bg-indigo-900/30 rounded-xl flex items-center justify-center text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Hash, { size: 24 }) }),
976          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
977            /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Unique Handle Generator" }),
978            /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Assign unique @handles to all users currently missing one." })
979          ] })
980        ] }),
981        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
982          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-50 dark:bg-zinc-950 rounded-xl border border-zinc-200 dark:border-zinc-800", children: [
983            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xs font-bold text-zinc-500 uppercase mb-3", children: "Handle Rules" }),
984            /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "text-sm text-zinc-600 dark:text-zinc-400 list-disc list-inside space-y-2", children: [
985              /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Characters: Alphanumeric, underscores, hyphens, and periods." }),
986              /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "No spaces allowed. All letters converted to lowercase." }),
987              /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Length: 3–30 characters." }),
988              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
989                /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Uniqueness:" }),
990                " If a collision occurs, a numeric suffix will be appended (e.g. @john, @john1)."
991              ] }),
992              /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
993                "Target: Only affects users where ",
994                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "handle" }),
995                " is currently ",
996                /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-200 dark:bg-zinc-800 px-1 rounded", children: "null" }),
997                "."
998              ] })
999            ] })
1000          ] }),
1001          /* @__PURE__ */ jsxRuntimeExports.jsxs(
1002            "button",
1003            {
1004              onClick: async () => {
1005                setIsProcessing(true);
1006                setLogs((prev) => [...prev, `Starting Unique Handle Generation...`]);
1007                try {
1008                  const res = await api.generateUserHandles();
1009                  setLogs((prev) => [...prev, `✓ Successfully processed ${res.processed} users (${res.conflicts} conflicts resolved).`]);
1010                  alert(`Migration Complete! Processed ${res.processed} users.`);
1011                } catch (e) {
1012                  setLogs((prev) => [...prev, `ERROR: ${e.message}`]);
1013                } finally {
1014                  setIsProcessing(false);
1015                }
1016              },
1017              disabled: isProcessing,
1018              className: "w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-indigo-500/20 disabled:opacity-50",
1019              children: [
1020                isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 20 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 20 }),
1021                isProcessing ? "Generating..." : "Start Handle Generation"
1022              ]
1023            }
1024          )
1025        ] })
1026      ] }) }) : activePreset === "archive_importer" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ArchiveImporter, {}) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1027        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 transition-all overflow-hidden ${isHeaderOpen ? "p-6" : "p-2"}`, children: isHeaderOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "animate-in fade-in slide-in-from-top-2", children: [
1028          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between mb-4", children: [
1029            /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white", children: "Connection & Source" }),
1030            /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsHeaderOpen(false), className: "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 18 }) })
1031          ] }),
1032          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-4 mb-4", children: [
1033            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
1034              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Source Table (MySQL)" }),
1035              /* @__PURE__ */ jsxRuntimeExports.jsx(
1036                "input",
1037                {
1038                  value: config.sourceTable,
1039                  onChange: (e) => setConfig({ ...config, sourceTable: e.target.value }),
1040                  placeholder: "e.g. nuke_users",
1041                  className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm font-mono text-zinc-900 dark:text-white focus:border-indigo-500 outline-none"
1042                }
1043              )
1044            ] }),
1045            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center text-zinc-400 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { size: 24 }) }),
1046            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
1047              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-1", children: "Target Model (Prisma)" }),
1048              /* @__PURE__ */ jsxRuntimeExports.jsxs(
1049                "select",
1050                {
1051                  value: config.targetModel,
1052                  onChange: (e) => setConfig({ ...config, targetModel: e.target.value }),
1053                  className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-2 text-sm font-mono text-zinc-900 dark:text-white focus:border-indigo-500 outline-none",
1054                  children: [
1055                    /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "-- Select Model --" }),
1056                    availableModels.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m, children: m }, m))
1057                  ]
1058                }
1059              )
1060            ] }),
1061            config.targetModel && /* @__PURE__ */ jsxRuntimeExports.jsx(
1062              "button",
1063              {
1064                onClick: () => handleResetDatabase(config.targetModel),
1065                disabled: isProcessing,
1066                className: "px-3 py-2 bg-red-50 dark:bg-red-900/10 text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/30 rounded-lg text-xs font-bold transition-colors mb-[1px]",
1067                title: `Clear ${config.targetModel} Table`,
1068                children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 16 })
1069              }
1070            )
1071          ] }),
1072          sourceMode === "direct" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-800/50 p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 mb-4 grid grid-cols-12 gap-3", children: [
1073            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-4", children: [
1074              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase mb-1", children: "Host" }),
1075              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: dbConfig.host, onChange: (e) => setDbConfig({ ...dbConfig, host: e.target.value }), className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-900 dark:text-white" })
1076            ] }),
1077            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
1078              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase mb-1", children: "Port" }),
1079              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: dbConfig.port, onChange: (e) => setDbConfig({ ...dbConfig, port: parseInt(e.target.value) }), className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-900 dark:text-white" })
1080            ] }),
1081            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
1082              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase mb-1", children: "User" }),
1083              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: dbConfig.user, onChange: (e) => setDbConfig({ ...dbConfig, user: e.target.value }), className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-900 dark:text-white" })
1084            ] }),
1085            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
1086              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase mb-1", children: "Password" }),
1087              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "password", value: dbConfig.password, onChange: (e) => setDbConfig({ ...dbConfig, password: e.target.value }), className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-900 dark:text-white" })
1088            ] }),
1089            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [
1090              /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase mb-1", children: "Database" }),
1091              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: dbConfig.database, onChange: (e) => setDbConfig({ ...dbConfig, database: e.target.value }), className: "w-full bg-white dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-900 dark:text-white" })
1092            ] })
1093          ] }),
1094          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [
1095            /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
1096              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setSourceMode("direct"), className: `px-3 py-1.5 rounded text-xs font-bold transition-colors ${sourceMode === "direct" ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`, children: "Direct DB Connection" }),
1097              /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setSourceMode("file"), className: `px-3 py-1.5 rounded text-xs font-bold transition-colors ${sourceMode === "file" ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400" : "text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`, children: "Upload Dump (JSON)" })
1098            ] }) }),
1099            sourceMode === "direct" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1100              "button",
1101              {
1102                onClick: handleDirectFetch,
1103                disabled: isProcessing || !config.sourceTable,
1104                className: "flex items-center gap-2 px-4 py-2 bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 rounded-lg text-sm font-bold hover:bg-zinc-200 dark:hover:bg-zinc-700 disabled:opacity-50",
1105                children: [
1106                  /* @__PURE__ */ jsxRuntimeExports.jsx(Server, { size: 14 }),
1107                  " Fetch Preview (First 50)"
1108                ]
1109              }
1110            ) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 px-4 py-2 bg-zinc-900 dark:bg-white text-white dark:text-black rounded-lg text-sm font-bold hover:opacity-90 cursor-pointer", children: [
1111              /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14 }),
1112              " Upload JSON",
1113              /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: ".json", onChange: handleFileUpload })
1114            ] })
1115          ] })
1116        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-xs text-zinc-500", children: [
1117          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1118            /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 14 }),
1119            /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
1120              dbConfig.host,
1121              " / ",
1122              dbConfig.database
1123            ] }),
1124            /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { size: 12 }),
1125            /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-700 dark:text-zinc-300", children: config.sourceTable || "No Source" })
1126          ] }),
1127          /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setIsHeaderOpen(true), className: "flex items-center gap-1 hover:text-zinc-900 dark:hover:text-white", children: [
1128            /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 12 }),
1129            " Configure"
1130          ] })
1131        ] }) }),
1132        /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex flex-col bg-zinc-50 dark:bg-black/20", children: sourceData.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1133          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-hidden flex flex-col md:flex-row", children: [
1134            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-6 border-r border-zinc-200 dark:border-zinc-800", children: [
1135              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [
1136                /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-bold text-zinc-900 dark:text-white flex items-center gap-2", children: [
1137                  /* @__PURE__ */ jsxRuntimeExports.jsx(Map$1, { size: 16, className: "text-indigo-500" }),
1138                  " Field Mapping"
1139                ] }),
1140                activePreset === "tracks" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col mx-4 p-4 bg-indigo-50 dark:bg-indigo-900/10 border border-indigo-200 dark:border-indigo-800/50 rounded-xl text-xs text-indigo-700 dark:text-indigo-400", children: [
1141                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
1142                    /* @__PURE__ */ jsxRuntimeExports.jsx(Server, { size: 18, className: "shrink-0 text-indigo-500" }),
1143                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm", children: "Smart Hierarchy Mapping Active" })
1144                  ] }),
1145                  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "mb-3 leading-relaxed", children: [
1146                    "The migration engine is now synchronized with the legacy ",
1147                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Gossamer Threads" }),
1148                    " hierarchy. It will automatically process categories to ensure your data is searchable and structured."
1149                  ] }),
1150                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3", children: [
1151                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-black/40 p-3 rounded-lg border border-indigo-100 dark:border-indigo-900/30", children: [
1152                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 uppercase font-bold mb-1", children: "Title Concatenation" }),
1153                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-indigo-600 dark:text-indigo-300", children: "[Artist] - [Category] - [Title]" }),
1154                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 mt-1 italic", children: "Deduplicated if Artist matches Category." })
1155                    ] }),
1156                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-black/40 p-3 rounded-lg border border-indigo-100 dark:border-indigo-900/30", children: [
1157                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 uppercase font-bold mb-1", children: "Tag Generation" }),
1158                      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1 mt-1", children: [
1159                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded", children: "Full Hierarchy" }),
1160                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 rounded", children: "Artist Note" }),
1161                        /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 rounded line-through", children: "Structural A-Z" })
1162                      ] })
1163                    ] })
1164                  ] }),
1165                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-zinc-500 dark:text-zinc-500", children: [
1166                    /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 14, className: "shrink-0" }),
1167                    /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px]", children: [
1168                      /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Top-most categories" }),
1169                      " (e.g. Broadway Musical) are automatically assigned to the ",
1170                      /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Genre" }),
1171                      " field."
1172                    ] })
1173                  ] })
1174                ] }),
1175                activePreset === "tags_legacy" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col mx-4 p-4 bg-emerald-50 dark:bg-emerald-900/10 border border-emerald-200 dark:border-emerald-800/50 rounded-xl text-xs text-emerald-700 dark:text-emerald-400", children: [
1176                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
1177                    /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { size: 18, className: "shrink-0 text-emerald-500" }),
1178                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm", children: "Explicit Tag Extraction Active" })
1179                  ] }),
1180                  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "mb-3 leading-relaxed", children: [
1181                    "This tool migrates manually assigned keywords from the legacy ",
1182                    /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "TagsLink" }),
1183                    " table. It joins with the main tags library to preserve community labeling."
1184                  ] }),
1185                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3", children: [
1186                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-black/40 p-3 rounded-lg border border-emerald-100 dark:border-emerald-900/30", children: [
1187                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 uppercase font-bold mb-1", children: "Deduplication" }),
1188                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-600 dark:text-emerald-300", children: "Slug-based Upsert" }),
1189                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 mt-1 italic", children: "Uses unique URL slugs to prevent duplicate records." })
1190                    ] }),
1191                    /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-black/40 p-3 rounded-lg border border-emerald-100 dark:border-emerald-900/30", children: [
1192                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 uppercase font-bold mb-1", children: "Relationship" }),
1193                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-mono text-emerald-600 dark:text-emerald-300", children: "MidiTag Link" }),
1194                      /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-400 mt-1 italic", children: "Automatically connects tags to tracks via many-to-many." })
1195                    ] })
1196                  ] })
1197                ] }),
1198                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
1199                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 bg-zinc-100 dark:bg-zinc-800 rounded-lg p-0.5 border border-zinc-200 dark:border-zinc-700", children: [
1200                    /* @__PURE__ */ jsxRuntimeExports.jsx(
1201                      "button",
1202                      {
1203                        onClick: () => setPreviewIndex(Math.max(0, previewIndex - 1)),
1204                        disabled: previewIndex === 0,
1205                        className: "p-1 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded disabled:opacity-30 text-zinc-600 dark:text-zinc-400",
1206                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 14 })
1207                      }
1208                    ),
1209                    /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-mono font-bold text-zinc-500 min-w-[60px] text-center", children: [
1210                      "Row ",
1211                      previewIndex + 1,
1212                      "/",
1213                      sourceData.length
1214                    ] }),
1215                    /* @__PURE__ */ jsxRuntimeExports.jsx(
1216                      "button",
1217                      {
1218                        onClick: () => setPreviewIndex(Math.min(sourceData.length - 1, previewIndex + 1)),
1219                        disabled: previewIndex === sourceData.length - 1,
1220                        className: "p-1 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded disabled:opacity-30 text-zinc-600 dark:text-zinc-400",
1221                        children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 })
1222                      }
1223                    )
1224                  ] }),
1225                  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500 hidden sm:block", children: [
1226                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold", children: config.sourceTable }),
1227                    " → ",
1228                    /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold", children: config.targetModel })
1229                  ] })
1230                ] })
1231              ] }),
1232              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [
1233                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-12 gap-2 text-xs font-bold text-zinc-500 uppercase px-3", children: [
1234                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-3", children: "Target Field" }),
1235                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-4", children: "Source Field" }),
1236                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-5", children: "Preview Value" })
1237                ] }),
1238                targetFields.map((target) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-12 gap-2 items-center bg-white dark:bg-zinc-900 p-2 rounded-lg border border-zinc-200 dark:border-zinc-800 shadow-sm", children: [
1239                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-3 font-mono text-xs font-bold text-zinc-700 dark:text-zinc-300 truncate", title: target, children: target }),
1240                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-4", children: activePreset === "comments" && (target === "text" || target === "likes" || target === "status") ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-500 italic", children: [
1241                    target === "text" && "Subject + Contents (Special)",
1242                    target === "likes" && "Helpful - NotHelpful (Special)",
1243                    target === "status" && "yes→visible, no→pending, del→hidden"
1244                  ] }) : activePreset === "tracks" && target === "status" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-500 italic", children: "Fixed: completed" }) : activePreset === "users" && target === "role" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded p-1.5 text-xs text-zinc-500 italic", children: "Default: USER" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
1245                    "select",
1246                    {
1247                      value: fieldMapping[target] || "",
1248                      onChange: (e) => updateMapping(target, e.target.value),
1249                      className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded p-1.5 text-xs outline-none focus:border-indigo-500 text-zinc-900 dark:text-white",
1250                      children: [
1251                        /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "-- Ignore --" }),
1252                        sourceFields.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: s, children: s }, s))
1253                      ]
1254                    }
1255                  ) }),
1256                  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-zinc-100 dark:bg-zinc-950/50 p-1.5 rounded text-xs font-mono text-zinc-500 truncate min-h-[24px]", children: currentTransformed[target] !== void 0 ? String(currentTransformed[target]) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-30 italic", children: "null" }) }) })
1257                ] }, target))
1258              ] })
1259            ] }),
1260            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full md:w-80 bg-zinc-100 dark:bg-zinc-950/50 p-4 border-t md:border-t-0 border-zinc-200 dark:border-zinc-800 flex flex-col", children: [
1261              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [
1262                /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white text-sm", children: "Source Record Inspector" }),
1263                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs bg-zinc-200 dark:bg-zinc-800 px-2 py-0.5 rounded text-zinc-600 dark:text-zinc-400", children: [
1264                  sourceData.length,
1265                  " records"
1266                ] })
1267              ] }),
1268              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between bg-white dark:bg-zinc-900 p-2 rounded-lg border border-zinc-200 dark:border-zinc-800 mb-4 shadow-sm", children: [
1269                /* @__PURE__ */ jsxRuntimeExports.jsx(
1270                  "button",
1271                  {
1272                    onClick: () => setPreviewIndex(Math.max(0, previewIndex - 1)),
1273                    disabled: previewIndex === 0,
1274                    className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded disabled:opacity-30",
1275                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 16 })
1276                  }
1277                ),
1278                /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-mono font-bold", children: [
1279                  "Record ",
1280                  previewIndex + 1,
1281                  " / ",
1282                  sourceData.length
1283                ] }),
1284                /* @__PURE__ */ jsxRuntimeExports.jsx(
1285                  "button",
1286                  {
1287                    onClick: () => setPreviewIndex(Math.min(sourceData.length - 1, previewIndex + 1)),
1288                    disabled: previewIndex === sourceData.length - 1,
1289                    className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded disabled:opacity-30",
1290                    children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 16 })
1291                  }
1292                )
1293              ] }),
1294              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-800 p-3 shadow-inner custom-scrollbar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[10px] font-mono text-zinc-600 dark:text-zinc-400 whitespace-pre-wrap break-all", children: JSON.stringify(currentRecord, null, 2) }) })
1295            ] })
1296          ] }),
1297          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-white dark:bg-zinc-900 border-t border-zinc-200 dark:border-zinc-800 flex justify-between items-center shrink-0", children: [
1298            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
1299              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1300                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase", children: "Migration Limit (0=All)" }),
1301                /* @__PURE__ */ jsxRuntimeExports.jsx(
1302                  "input",
1303                  {
1304                    type: "number",
1305                    min: 0,
1306                    value: fetchLimit,
1307                    onChange: (e) => setFetchLimit(parseInt(e.target.value)),
1308                    className: "w-24 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded p-1 text-sm outline-none text-zinc-900 dark:text-white"
1309                  }
1310                )
1311              ] }),
1312              /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1313                /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-500 uppercase", children: "Starting ID (0=All)" }),
1314                /* @__PURE__ */ jsxRuntimeExports.jsx(
1315                  "input",
1316                  {
1317                    type: "number",
1318                    min: 0,
1319                    value: fetchStartId,
1320                    onChange: (e) => setFetchStartId(parseInt(e.target.value)),
1321                    className: "w-24 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded p-1 text-sm outline-none text-zinc-900 dark:text-white"
1322                  }
1323                )
1324              ] }),
1325              /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 w-px bg-zinc-200 dark:bg-zinc-800 mx-2" }),
1326              catchUpData && !overwrite ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs shrink-0 max-w-[250px]", children: [
1327                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-bold text-indigo-600 dark:text-indigo-400 flex items-center gap-1", children: [
1328                  /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 12, className: "text-emerald-500" }),
1329                  " Catch-Up Stats"
1330                ] }),
1331                /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-zinc-500 whitespace-nowrap overflow-hidden text-ellipsis", children: [
1332                  "Total: ",
1333                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-700 dark:text-zinc-300", children: catchUpData.totalSourceRows }),
1334                  " | New: ",
1335                  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-emerald-600 dark:text-emerald-400", children: [
1336                    "+",
1337                    catchUpData.newRowsCount
1338                  ] }),
1339                  " | Skip: ",
1340                  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-zinc-500", children: catchUpData.existingRowsCount })
1341                ] })
1342              ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500", children: [
1343                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-indigo-600 dark:text-indigo-400", children: "Server-Side Execution" }),
1344                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Data will be streamed directly between databases." })
1345              ] })
1346            ] }),
1347            /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
1348              !overwrite && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1349                "button",
1350                {
1351                  onClick: handleCalculateCatchUp,
1352                  disabled: isProcessing || isCountingCatchUp,
1353                  className: "px-3 py-1.5 bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-950 dark:hover:bg-indigo-900 text-indigo-600 dark:text-indigo-400 rounded-lg text-xs font-bold flex items-center gap-1.5 transition-colors disabled:opacity-50",
1354                  title: "Check how many new rows will be added to catch-up before starting migration",
1355                  children: [
1356                    isCountingCatchUp ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersVertical, { size: 12 }),
1357                    "Check Catch-Up Rows"
1358                  ]
1359                }
1360              ),
1361              /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer group", title: "Overwrite existing records with matching IDs", children: [
1362                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-8 h-4 rounded-full transition-colors relative ${overwrite ? "bg-indigo-500" : "bg-zinc-300 dark:bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform ${overwrite ? "translate-x-4" : ""}` }) }),
1363                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", className: "hidden", checked: overwrite, onChange: (e) => setOverwrite(e.target.checked) }),
1364                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 group-hover:text-indigo-600 transition-colors", children: "Overwrite Existing" })
1365              ] }),
1366              /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer group", title: "Log SQL queries and Prisma operations to the console", children: [
1367                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-8 h-4 rounded-full transition-colors relative ${verbose ? "bg-amber-500" : "bg-zinc-300 dark:bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform ${verbose ? "translate-x-4" : ""}` }) }),
1368                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", className: "hidden", checked: verbose, onChange: (e) => {
1369                  setVerbose(e.target.checked);
1370                  if (e.target.checked) {
1371                    setVerboseErrorOnly(false);
1372                  }
1373                } }),
1374                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 group-hover:text-amber-600 transition-colors", children: "Verbose Log" })
1375              ] }),
1376              /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer group", title: "Filter verbose logs to show only warning, skip, alignment, and error details", children: [
1377                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-8 h-4 rounded-full transition-colors relative ${verboseErrorOnly ? "bg-rose-500" : "bg-zinc-300 dark:bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform ${verboseErrorOnly ? "translate-x-4" : ""}` }) }),
1378                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", className: "hidden", checked: verboseErrorOnly, onChange: (e) => {
1379                  setVerboseErrorOnly(e.target.checked);
1380                  if (e.target.checked) {
1381                    setVerbose(false);
1382                  }
1383                } }),
1384                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 group-hover:text-rose-600 transition-colors", children: "Errors Only" })
1385              ] }),
1386              config.targetModel && /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer group", title: `Wipe all records from the ${config.targetModel} table before migrating`, children: [
1387                /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-8 h-4 rounded-full transition-colors relative ${resetBeforeMigrating ? "bg-red-500" : "bg-zinc-300 dark:bg-zinc-700"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform ${resetBeforeMigrating ? "translate-x-4" : ""}` }) }),
1388                /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", className: "hidden", checked: resetBeforeMigrating, onChange: (e) => setResetBeforeMigrating(e.target.checked) }),
1389                /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-500 group-hover:text-red-500 transition-colors", children: "Reset Table" })
1390              ] }),
1391              /* @__PURE__ */ jsxRuntimeExports.jsxs(
1392                "button",
1393                {
1394                  onClick: handleExecute,
1395                  disabled: isProcessing,
1396                  className: "px-6 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-bold flex items-center gap-2 shadow-lg shadow-emerald-500/20 disabled:opacity-50",
1397                  children: [
1398                    isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 16 }),
1399                    isProcessing ? "Migrating..." : "Start Migration"
1400                  ]
1401                }
1402              )
1403            ] })
1404          ] })
1405        ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center flex-1 text-zinc-400", children: [
1406          /* @__PURE__ */ jsxRuntimeExports.jsx(Database, { size: 48, className: "mb-4 opacity-20" }),
1407          /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Configure source and fetch preview to begin mapping." })
1408        ] }) })
1409      ] }),
1410      /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border-t border-zinc-200 dark:border-zinc-800 bg-black transition-all ${isLogsOpen ? "h-48" : "h-8"} flex flex-col`, children: [
1411        /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-8 flex items-center justify-between px-4 bg-zinc-900 border-b border-zinc-800 cursor-pointer", onClick: () => setIsLogsOpen(!isLogsOpen), children: [
1412          /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] font-bold text-zinc-500 uppercase flex items-center gap-2", children: [
1413            /* @__PURE__ */ jsxRuntimeExports.jsx(Server, { size: 10 }),
1414            " Migration Console"
1415          ] }),
1416          isLogsOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14, className: "text-zinc-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14, className: "text-zinc-500" })
1417        ] }),
1418        isLogsOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-4 font-mono text-xs text-zinc-400 custom-scrollbar animate-fadeIn", children: [
1419          (() => {
1420            const displayedLogs = verboseErrorOnly ? logs.filter(
1421              (log) => log.startsWith("[ERROR]") || log.startsWith("[SKIP]") || log.startsWith("[ALIGN ID]") || log.startsWith("Starting") || log.startsWith("✓") || log.startsWith("ERROR:") || log.startsWith("WARNING:") || log.startsWith("SUCCESS:")
1422            ) : logs;
1423            if (displayedLogs.length === 0) {
1424              return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "opacity-30", children: "Waiting for actions..." });
1425            }
1426            return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1427              displayedLogs.map((log, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mb-1 ${log.startsWith("[ERROR]") || log.startsWith("ERROR:") ? "text-rose-400 font-bold" : log.startsWith("[SKIP]") ? "text-amber-400 font-semibold" : log.startsWith("[ALIGN ID]") ? "text-sky-400 font-semibold" : log.startsWith("✓") ? "text-emerald-400 font-bold" : "text-zinc-400"}`, children: log }, i)),
1428              verboseErrorOnly && logs.some((l) => l.startsWith("[OK]")) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-600 italic text-[10px] mt-2 border-t border-zinc-900/50 pt-1", children: '* Standard [OK] database operations have been filtered from view. Uncheck "Errors Only" to inspect them.' })
1429            ] });
1430          })(),
1431          successCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-emerald-500 font-bold mt-2", children: [
1432            "✓ Operation completed: ",
1433            successCount,
1434            " records processed."
1435          ] })
1436        ] })
1437      ] })
1438    ] })
1439  ] });
1440};
1441export {
1442  DataMigration as default
1443};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.