<script setup>
import AppLayout from "@/Layouts/AppLayout.vue";
import { ref, computed, onMounted, watch } from "vue";
import Multiselect from "vue-multiselect";
import axios from "axios";

const props = defineProps({
  import: Object,
  templates: Array,
  clients: Array,
  client: Object,
});

const importId = ref(props.import?.id || null);
const hasHeader = ref(Boolean(props.import?.meta?.has_header ?? true));
const detected = ref({
  columns: props.import?.meta?.columns || [],
  delimiter: props.import?.meta?.detected_delimiter || ",",
  has_header: hasHeader.value,
});
const templateApplied = ref(false);
const processing = ref(false);
const processResult = ref(null);
const mappingRows = ref([]);
const mappingSaved = ref(false);
const mappingSavedCount = ref(0);
const mappingError = ref("");
const savingMappings = ref(false);
// Persisted mappings from backend (raw view regardless of detected columns)
const persistedMappings = ref([]);
const detectedNote = ref("");
// Delimiter selection (auto by default, can be overridden by template or user)
const delimiterState = ref({ mode: "auto", custom: "" });
const effectiveDelimiter = computed(() => {
  switch (delimiterState.value.mode) {
    case "auto":
      return null; // let backend detect
    case "comma":
      return ",";
    case "semicolon":
      return ";";
    case "tab":
      return "\t";
    case "pipe":
      return "|";
    case "space":
      return " ";
    case "custom":
      return delimiterState.value.custom || null;
    default:
      return null;
  }
});
// Initialize delimiter from import meta if previously chosen
const initForced = props.import?.meta?.forced_delimiter || null;
if (initForced) {
  const map = { ",": "comma", ";": "semicolon", "\t": "tab", "|": "pipe", " ": "space" };
  const mode = map[initForced] || "custom";
  delimiterState.value.mode = mode;
  if (mode === "custom") delimiterState.value.custom = initForced;
}
// Logs
const events = ref([]);
const eventsLimit = ref(200);
const loadingEvents = ref(false);

// Completed status helper
const isCompleted = computed(() => (props.import?.status || "") === "completed");

// Whether backend has any saved mappings for this import
const hasPersistedMappings = computed(() => (persistedMappings.value?.length || 0) > 0);
const canProcess = computed(
  () =>
    !!importId.value &&
    !processing.value &&
    hasPersistedMappings.value &&
    !isCompleted.value
);

// Dynamic entity definitions and options fetched from API
const entityDefs = ref([]); // [{ key, label, canonical_root, fields: [] }]
const usingEntityFallback = ref(false);
const entityOptions = computed(() =>
  entityDefs.value.map((e) => ({ value: e.key, label: e.label || e.key }))
);
const fieldOptionsByEntity = computed(() =>
  Object.fromEntries(
    entityDefs.value.map((e) => [e.key, Array.isArray(e.fields) ? e.fields : []])
  )
);
const canonicalRootByKey = computed(() =>
  Object.fromEntries(entityDefs.value.map((e) => [e.key, e.canonical_root || e.key]))
);
const keyByCanonicalRoot = computed(() => {
  const m = {};
  for (const e of entityDefs.value) {
    if (e.canonical_root) {
      m[e.canonical_root] = e.key;
    }
  }
  return m;
});

// Provide fields for either a UI key (e.g. "contracts") or a canonical root (e.g. "contract")
function fieldsForEntity(entityOrCanonical) {
  if (!entityOrCanonical) return [];
  // Try direct key first
  const direct = fieldOptionsByEntity.value[entityOrCanonical];
  if (Array.isArray(direct)) return direct;
  // If a canonical root was stored before entityDefs loaded, map it now
  const byKey = keyByCanonicalRoot.value[entityOrCanonical];
  if (byKey) {
    const via = fieldOptionsByEntity.value[byKey];
    if (Array.isArray(via)) return via;
  }
  return [];
}

function defaultEntityDefs() {
  return [
    {
      key: "person",
      label: "Person",
      canonical_root: "person",
      fields: [
        "first_name",
        "last_name",
        "full_name",
        "gender",
        "birthday",
        "tax_number",
        "social_security_number",
        "description",
      ],
    },
    {
      key: "person_addresses",
      label: "Person Addresses",
      canonical_root: "address",
      fields: ["address", "country", "type_id", "description"],
    },
    {
      key: "person_phones",
      label: "Person Phones",
      canonical_root: "phone",
      fields: ["nu", "country_code", "type_id", "description"],
    },
    {
      key: "emails",
      label: "Emails",
      canonical_root: "email",
      fields: ["value", "is_primary", "label"],
    },
    {
      key: "contracts",
      label: "Contracts",
      canonical_root: "contract",
      fields: [
        "reference",
        "start_date",
        "end_date",
        "description",
        "type_id",
        "client_case_id",
      ],
    },
    {
      key: "accounts",
      label: "Accounts",
      canonical_root: "account",
      fields: [
        "reference",
        "balance_amount",
        "contract_id",
        "contract_reference",
        "type_id",
        "active",
        "description",
      ],
    },
  ];
}

async function loadEntityDefs() {
  try {
    const { data } = await axios.get("/api/import-entities");
    const items = Array.isArray(data?.entities) ? data.entities : [];
    // Normalize fields to arrays and ensure labels
    const normalized = items.map((e) => ({
      key: e.key,
      label: e.label || e.key,
      canonical_root: e.canonical_root || e.key,
      fields: Array.isArray(e.fields) ? e.fields : [],
    }));
    if (!normalized.length) {
      usingEntityFallback.value = true;
      entityDefs.value = defaultEntityDefs();
    } else {
      usingEntityFallback.value = false;
      entityDefs.value = normalized;
    }
    // Normalize any existing mapping row entity values to UI keys if they are canonical roots
    if (Array.isArray(mappingRows.value) && mappingRows.value.length) {
      const mapCanonToKey = keyByCanonicalRoot.value;
      mappingRows.value = mappingRows.value.map((r) => {
        const current = r.entity;
        if (current && !entityDefs.value.find((e) => e.key === current)) {
          const maybeKey = mapCanonToKey[current];
          if (maybeKey) {
            return { ...r, entity: maybeKey };
          }
        }
        return r;
      });
    }
  } catch (e) {
    console.error("Failed to load import entity definitions", e);
    usingEntityFallback.value = true;
    entityDefs.value = defaultEntityDefs();
    // Also normalize with fallback
    if (Array.isArray(mappingRows.value) && mappingRows.value.length) {
      const mapCanonToKey = keyByCanonicalRoot.value;
      mappingRows.value = mappingRows.value.map((r) => {
        const current = r.entity;
        if (current && !entityDefs.value.find((e) => e.key === current)) {
          const maybeKey = mapCanonToKey[current];
          if (maybeKey) {
            return { ...r, entity: maybeKey };
          }
        }
        return r;
      });
    }
  }
}

// Display rows used by the table: prefer mappingRows if present; otherwise fall back to detected columns
const displayRows = computed(() => {
  if (Array.isArray(mappingRows.value) && mappingRows.value.length > 0) {
    return mappingRows.value;
  }
  const cols = detected.value?.columns || [];
  return cols.map((c, idx) => ({
    source_column: c,
    entity: "",
    field: "",
    skip: false,
    transform: "trim",
    apply_mode: "both",
    position: idx,
  }));
});

// Header normalization and guess mapping for auto-assigning sensible defaults
function stripDiacritics(s) {
  if (!s) return "";
  return String(s)
    .replace(/[čć]/gi, "c")
    .replace(/[š]/gi, "s")
    .replace(/[ž]/gi, "z")
    .replace(/[đ]/gi, "d")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");
}
function normalizeHeader(h) {
  if (!h) return "";
  const base = stripDiacritics(String(h)).toLowerCase();
  return base
    .replace(/[^a-z0-9]+/g, " ")
    .trim()
    .replace(/\s+/g, "");
}

// Frontend auto-guessing disabled: entity/field will remain blank unless loaded from persisted mappings or set by the user
function guessMappingForHeader(h) {
  return null;
}

// Normalize source column to match persisted mappings in a case/space/diacritic-insensitive way
function normalizeSource(s) {
  return normalizeHeader(s);
}

// Entity and field options are provided dynamically by API via entityOptions and fieldOptionsByEntity

// Local state for selects
const form = ref({
  client_uuid: props.client.uuid,
  import_template_id: props.import?.import_template_id || null,
});

// Initialize client_uuid from import.client_uuid (preferred) using provided clients list
if (props.import?.client_uuid) {
  const found = (props.clients || []).find((c) => c.uuid === props.import.client_uuid);
  form.value.client_uuid = found ? found.uuid : null;
}

const selectedClientOption = computed({
  get() {
    const cuuid = form.value.client_uuid;
    if (!cuuid) return null;
    return (props.clients || []).find((c) => c.uuid === cuuid) || null;
  },
  set(val) {
    form.value.client_uuid = val ? val.uuid : null;
  },
});

const selectedTemplateOption = computed({
  get() {
    const tid = form.value.import_template_id;
    if (tid == null) return null;
    return (props.templates || []).find((t) => t.id === tid) || null;
  },
  set(val) {
    form.value.import_template_id = val ? val.id : null;
  },
});

// Show only global templates when no client is selected.
// When a client is selected, show only that client's templates (strict match by client_uuid, no globals).
const filteredTemplates = computed(() => {
  const cuuid = form.value.client_uuid;
  const list = props.templates || [];
  if (!cuuid) {
    return list.filter((t) => t.client_id == null);
  }
  return list.filter((t) => t.client_uuid && t.client_uuid === cuuid);
});

const selectedMappingsCount = computed(
  () => mappingRows.value.filter((r) => !r.skip && r.entity && r.field).length
);

async function fetchColumns() {
  if (!importId.value) return;
  const url = route("imports.columns", { import: importId.value });
  const params = { has_header: hasHeader.value ? 1 : 0 };
  if (effectiveDelimiter.value) {
    params.delimiter = effectiveDelimiter.value;
  }
  const { data } = await axios.get(url, { params });
  // Normalize columns to strings for consistent rendering
  const colsRaw = Array.isArray(data.columns) ? data.columns : [];
  const normCols = colsRaw
    .map((c) => {
      if (typeof c === "string" || typeof c === "number") return String(c);
      if (c && typeof c === "object") {
        return String(c.name ?? c.header ?? c.label ?? Object.values(c)[0] ?? "");
      }
      return "";
    })
    .filter(Boolean);
  detected.value = {
    columns: normCols,
    delimiter: data.detected_delimiter || ",",
    has_header: !!data.has_header,
  };

  console.table(colsRaw, normCols);
  detectedNote.value = data.note || "";
  // initialize mapping rows if empty
  if (!mappingRows.value.length && detected.value.columns.length) {
    mappingRows.value = detected.value.columns.map((c, idx) => ({
      source_column: c,
      entity: "",
      field: "",
      skip: false,
      transform: "trim",
      apply_mode: "both",
      position: idx,
    }));
  }
  await loadImportMappings();
  // Fallback: if no detected columns were found, but persisted mappings exist, use them to render the grid
  if (
    (!detected.value.columns || detected.value.columns.length === 0) &&
    mappingRows.value.length === 0 &&
    persistedMappings.value.length > 0
  ) {
    mappingRows.value = persistedMappings.value.map((m, idx) => {
      const tf = String(m.target_field || "");
      const [record, field] = tf ? tf.split(".", 2) : ["", ""];
      return {
        source_column: m.source_column,
        entity: recordToEntityKey(record),
        field: field || "",
        skip: false,
        transform: m.transform || "trim",
        apply_mode: m.apply_mode || "both",
        position: idx,
      };
    });
  }
}

async function applyTemplateToImport() {
  if (!importId.value || !form.value.import_template_id) return;
  try {
    if (templateApplied.value) {
      const ok = window.confirm(
        'Re-apply this template? This will overwrite current mappings for this import.'
      );
      if (!ok) {
        return;
      }
    }
    await axios.post(
      route("importTemplates.apply", {
        template: form.value.import_template_id,
        import: importId.value,
      }),
      {},
      {
        headers: { Accept: "application/json" },
        withCredentials: true,
      }
    );
    templateApplied.value = true;
    // If template has a default delimiter, adopt it and refetch columns
    const tpl = selectedTemplateOption.value;
    const tplDelim = tpl?.delimiter || tpl?.meta?.delimiter || null;
    if (tplDelim) {
      // map to known mode if possible, else set custom
      const map = {
        ",": "comma",
        ";": "semicolon",
        "\t": "tab",
        "|": "pipe",
        " ": "space",
      };
      const mode = map[tplDelim] || "custom";
      delimiterState.value.mode = mode;
      if (mode === "custom") delimiterState.value.custom = tplDelim;
      await fetchColumns();
    }
    await loadImportMappings();
  } catch (e) {
    templateApplied.value = false;
    console.error(
      "Apply template error",
      e.response?.status || "",
      e.response?.data || e
    );
  }
}

async function loadImportMappings() {
  if (!importId.value) return;
  try {
    const { data } = await axios.get(
      route("imports.mappings.get", { import: importId.value }),
      {
        headers: { Accept: "application/json" },
        withCredentials: true,
      }
    );
    console.log(data);
    const rows = Array.isArray(data?.mappings) ? data.mappings : [];

    // Store raw persisted mappings for display regardless of detected columns
    persistedMappings.value = rows.slice();
    if (!rows.length) return;
    const bySource = new Map(rows.map((r) => [normalizeSource(r.source_column), r]));
    mappingRows.value = (mappingRows.value || []).map((r, idx) => {
      const m = bySource.get(normalizeSource(r.source_column));
      if (!m) return r;
      const tf = String(m.target_field || "");
      let entity = m.entity || "";
      let field = r.field || "";
      if (tf) {
        const [record, fld] = tf.split(".", 2);
        const inferred = recordToEntityKey(record);
        if (!entity) entity = inferred;
        if (fld) field = fld;
      }
      return {
        ...r,
        entity,
        field,
        transform: m.transform || "",
        apply_mode: m.apply_mode || "both",
        skip: false,
        position: idx,
      };
    });
  } catch (e) {
    console.error(
      "Load import mappings error",
      e.response?.status || "",
      e.response?.data || e
    );
  }
}

async function saveMappings() {
  if (!importId.value) return;
  mappingError.value = "";
  const mappings = mappingRows.value
    .filter((r) => !r.skip && r.entity && r.field)
    .map((r) => ({
      source_column: r.source_column,
      entity: r.entity || null,
      target_field: `${entityKeyToRecord(r.entity)}.${r.field}`,
      transform: r.transform || null,
      apply_mode: r.apply_mode || "both",
      options: null,
    }));
  if (!mappings.length) {
    mappingSaved.value = false;
    mappingError.value =
      "Select entity and field for at least one column (or uncheck Skip) before saving.";
    return;
  }
  try {
    savingMappings.value = true;
    const url = route("imports.mappings.save", { import: importId.value });
    const { data } = await axios.post(
      url,
      { mappings },
      {
        headers: { Accept: "application/json" },
        withCredentials: true,
      }
    );
    mappingSaved.value = true;
    mappingSavedCount.value = Number(data?.saved || mappings.length);
    mappingError.value = "";
    // Refresh persisted mappings so Process gating reflects the actual DB state
    await loadImportMappings();
  } catch (e) {
    mappingSaved.value = false;
    console.error("Save mappings error", e.response?.status || "", e.response?.data || e);
  } finally {
    savingMappings.value = false;
  }
}

async function processImport() {
  if (!importId.value) return;
  processing.value = true;
  processResult.value = null;
  try {
    const { data } = await axios.post(
      route("imports.process", { import: importId.value }),
      {},
      {
        headers: { Accept: "application/json" },
        withCredentials: true,
      }
    );
    processResult.value = data;
  } catch (e) {
    console.error(
      "Process import error",
      e.response?.status || "",
      e.response?.data || e
    );
    processResult.value = { error: "Processing failed" };
  } finally {
    processing.value = false;
  }
}

// Helpers using canonical roots from API
function entityKeyToRecord(key) {
  return canonicalRootByKey.value[key] || key;
}
function recordToEntityKey(record) {
  return keyByCanonicalRoot.value[record] || record;
}

// Initial load
onMounted(async () => {
  await loadEntityDefs();
  // Build mapping grid from existing meta columns if present
  if (detected.value.columns?.length) {
    mappingRows.value = (detected.value.columns || [])
      // keep only defined, non-empty values (after trimming)
      .filter((c) => c !== undefined && c !== null && String(c).trim() !== "")
      .map((c, idx) => {
        return {
          source_column: c,
          entity: "",
          field: "",
          skip: false,
          transform: "trim",
          apply_mode: "both",
          position: idx,
        };
      });

    await loadImportMappings();
    console.log(mappingRows.value);
  } else {
    await fetchColumns();
  }
  // Auto-apply template mapping once if a template is selected and not already applied
  console.log(templateApplied.value);
  try {
    if (!templateApplied.value && form.value.import_template_id && importId.value) {
      await applyTemplateToImport();
    }
  } catch (e) {
    console.warn('Auto apply template failed', e);
  }
  // Load recent events (logs)
  await fetchEvents();
});

// Reset saved flag whenever user edits mappings
watch(
  mappingRows,
  () => {
    mappingSaved.value = false;
    mappingSavedCount.value = 0;
    mappingError.value = "";
  },
  { deep: true }
);

// If detected columns are loaded after mount, initialize mapping rows once
watch(
  () => detected.value.columns,
  (cols) => {
    if (Array.isArray(cols) && cols.length > 0 && mappingRows.value.length === 0) {
      mappingRows.value = cols.map((c, idx) => {
        return {
          source_column: c,
          entity: "",
          field: "",
          skip: false,
          transform: "trim",
          apply_mode: "both",
          position: idx,
        };
      });
    }
  }
);

// If user changes delimiter selection, refresh detected columns
watch(
  () => delimiterState.value,
  async () => {
    if (importId.value) {
      await fetchColumns();
    }
  },
  { deep: true }
);

async function fetchEvents() {
  if (!importId.value) return;
  loadingEvents.value = true;
  try {
    const { data } = await axios.get(
      route("imports.events", { import: importId.value }),
      {
        params: { limit: eventsLimit.value },
        headers: { Accept: "application/json" },
        withCredentials: true,
      }
    );
    events.value = Array.isArray(data?.events) ? data.events : [];
  } catch (e) {
    console.error(
      "Load import events error",
      e.response?.status || "",
      e.response?.data || e
    );
  } finally {
    loadingEvents.value = false;
  }
}
</script>

<template>
  <AppLayout :title="`Import ${props.import?.uuid || ''}`">
    <template #header>
      <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-1">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">Continue Import</h2>
        <div class="text-sm text-gray-600">
          <span class="mr-4">Client:
            <strong>{{ selectedClientOption?.name || selectedClientOption?.uuid || "—" }}</strong>
          </span>
          <span
            v-if="templateApplied"
            class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 align-middle"
          >applied</span>
        </div>
      </div>
    </template>
    <div class="py-6">
      <div class="max-w-5xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white shadow sm:rounded-lg p-6 space-y-6">
          <div v-if="isCompleted" class="p-3 border rounded bg-gray-50 text-sm">
            <div class="flex flex-wrap gap-x-6 gap-y-1">
              <div>
                <span class="text-gray-600">Status:</span>
                <span class="font-medium text-emerald-700">Completed</span>
              </div>
              <div>
                <span class="text-gray-600">Finished:</span>
                <span class="font-medium">{{
                  props.import?.finished_at
                    ? new Date(props.import.finished_at).toLocaleString()
                    : "—"
                }}</span>
              </div>
              <div>
                <span class="text-gray-600">Total:</span>
                <span class="font-medium">{{ props.import?.total_rows ?? "—" }}</span>
              </div>
              <div>
                <span class="text-gray-600">Imported:</span>
                <span class="font-medium">{{ props.import?.imported_rows ?? "—" }}</span>
              </div>
              <div>
                <span class="text-gray-600">Invalid:</span>
                <span class="font-medium">{{ props.import?.invalid_rows ?? "—" }}</span>
              </div>
              <div>
                <span class="text-gray-600">Valid:</span>
                <span class="font-medium">{{ props.import?.valid_rows ?? "—" }}</span>
              </div>
            </div>
          </div>
          <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
            <div>
              <label class="block text-sm font-medium text-gray-700">Client</label>
              <Multiselect
                v-model="selectedClientOption"
                :options="clients"
                track-by="uuid"
                label="name"
                placeholder="Search clients..."
                :searchable="true"
                :allow-empty="true"
                class="mt-1"
                disabled
              />
              <p class="text-xs text-gray-500 mt-1">Client is set during upload.</p>
            </div>
            <div>
              <label class="block text-sm font-medium text-gray-700">Template</label>
              <Multiselect
                v-model="selectedTemplateOption"
                :options="filteredTemplates"
                track-by="id"
                label="name"
                placeholder="Search templates..."
                :searchable="true"
                :allow-empty="true"
                class="mt-1"
                :disabled="false"
              >
                <template #option="{ option }">
                  <div class="flex items-center justify-between w-full">
                    <div class="flex items-center gap-2">
                      <span>{{ option.name }}</span>
                      <span class="ml-2 text-xs text-gray-500"
                        >({{ option.source_type }})</span
                      >
                    </div>
                    <span
                      class="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600"
                      >{{ option.client_id ? "Client" : "Global" }}</span
                    >
                  </div>
                </template>
                <template #singleLabel="{ option }">
                  <div class="flex items-center gap-2">
                    <span>{{ option.name }}</span>
                    <span class="ml-1 text-xs text-gray-500"
                      >({{ option.source_type }})</span
                    >
                    <span
                      class="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600"
                      >{{ option.client_id ? "Client" : "Global" }}</span
                    >
                  </div>
                </template>
              </Multiselect>
            </div>
          </div>

          <!-- Parsing options -->
          <div
            class="grid grid-cols-1 sm:grid-cols-3 gap-4 items-end"
            v-if="!isCompleted"
          >
            <div>
              <label class="block text-sm font-medium text-gray-700">Header row</label>
              <select
                v-model="hasHeader"
                class="mt-1 block w-full border rounded p-2"
                @change="fetchColumns"
              >
                <option :value="true">Has header</option>
                <option :value="false">No header (positional)</option>
              </select>
            </div>
            <div>
              <label class="block text-sm font-medium text-gray-700">Delimiter</label>
              <select
                v-model="delimiterState.mode"
                class="mt-1 block w-full border rounded p-2"
              >
                <option value="auto">Auto-detect</option>
                <option value="comma">Comma ,</option>
                <option value="semicolon">Semicolon ;</option>
                <option value="tab">Tab \t</option>
                <option value="pipe">Pipe |</option>
                <option value="space">Space ␠</option>
                <option value="custom">Custom…</option>
              </select>
              <p class="text-xs text-gray-500 mt-1">
                Template default: {{ selectedTemplateOption?.meta?.delimiter || "auto" }}
              </p>
            </div>
            <div v-if="delimiterState.mode === 'custom'">
              <label class="block text-sm font-medium text-gray-700"
                >Custom delimiter</label
              >
              <input
                v-model="delimiterState.custom"
                maxlength="4"
                placeholder=","
                class="mt-1 block w-full border rounded p-2"
              />
            </div>
          </div>

          <div class="flex gap-3" v-if="!isCompleted">
            <button
              @click.prevent="applyTemplateToImport"
              :disabled="!importId || !form.import_template_id"
              class="px-4 py-2 bg-emerald-600 disabled:bg-gray-300 text-white rounded"
            >
              {{ templateApplied ? 'Re-apply Template' : 'Apply Template' }}
            </button>
            <button
              @click.prevent="saveMappings"
              :disabled="!importId || processing || savingMappings || isCompleted"
              class="px-4 py-2 bg-orange-600 disabled:bg-gray-300 text-white rounded flex items-center gap-2"
              title="Save ad-hoc mappings for this import"
            >
              <span
                v-if="savingMappings"
                class="inline-block h-4 w-4 border-2 border-white/70 border-t-transparent rounded-full animate-spin"
              ></span>
              <span>Save Mappings</span>
              <span
                v-if="selectedMappingsCount"
                class="ml-1 text-xs bg-white/20 px-1.5 py-0.5 rounded"
                >{{ selectedMappingsCount }}</span
              >
            </button>
            <button
              @click.prevent="processImport"
              :disabled="!canProcess"
              class="px-4 py-2 bg-purple-600 disabled:bg-gray-300 text-white rounded"
            >
              {{ processing ? "Processing…" : "Process Import" }}
            </button>
          </div>
          <div class="mt-2 text-xs text-gray-600" v-if="!importId">Import not found.</div>
          <div
            class="mt-2 text-xs text-gray-600"
            v-else-if="importId && !hasPersistedMappings && !isCompleted"
          >
            Apply a template or select Entity and Field for one or more columns, then
            click Save Mappings to enable processing.
          </div>

          <div v-if="persistedMappings.length" class="pt-4">
            <h3 class="font-semibold mb-2">Current Saved Mappings</h3>
            <div class="overflow-x-auto">
              <table class="min-w-full border bg-white text-sm">
                <thead>
                  <tr class="bg-gray-50 text-left text-xs uppercase text-gray-600">
                    <th class="p-2 border">Source column</th>
                    <th class="p-2 border">Target field</th>
                    <th class="p-2 border">Transform</th>
                    <th class="p-2 border">Mode</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="m in persistedMappings" :key="m.id" class="border-t">
                    <td class="p-2 border">{{ m.source_column }}</td>
                    <td class="p-2 border">{{ m.target_field }}</td>
                    <td class="p-2 border">{{ m.transform || "—" }}</td>
                    <td class="p-2 border">{{ m.apply_mode || "both" }}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>

          <div v-if="!isCompleted && displayRows.length" class="pt-4">
            <h3 class="font-semibold mb-2">
              <template v-if="!isCompleted"
                >Detected Columns ({{ detected.has_header ? "header" : "positional" }})
                <span class="ml-2 text-xs text-gray-500"
                  >detected: {{ detected.columns.length }}, rows:
                  {{ displayRows.length }}, delimiter:
                  {{ detected.delimiter || "auto" }}</span
                >
              </template>
              <template v-else>Detected Columns</template>
            </h3>
            <p v-if="detectedNote" class="text-xs text-gray-500 mb-2">
              {{ detectedNote }}
            </p>
            <div class="overflow-x-auto">
              <table class="min-w-full border bg-white">
                <thead>
                  <tr class="bg-gray-50 text-left text-xs uppercase text-gray-600">
                    <th class="p-2 border">Source column</th>
                    <th class="p-2 border">Entity</th>
                    <th class="p-2 border">Field</th>
                    <th class="p-2 border">Transform</th>
                    <th class="p-2 border">Apply mode</th>
                    <th class="p-2 border">Skip</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="(row, idx) in displayRows" :key="idx" class="border-t">
                    <td class="p-2 border text-sm">{{ row.source_column }}</td>
                    <td class="p-2 border">
                      <select
                        v-model="row.entity"
                        class="border rounded p-1 w-full"
                        :disabled="isCompleted"
                      >
                        <option value="">—</option>
                        <option
                          v-for="opt in entityOptions"
                          :key="opt.value"
                          :value="opt.value"
                        >
                          {{ opt.label }}
                        </option>
                      </select>
                    </td>
                    <td class="p-2 border">
                      <select
                        v-model="row.field"
                        class="border rounded p-1 w-full"
                        :disabled="isCompleted"
                      >
                        <option value="">—</option>
                        <option
                          v-for="f in fieldsForEntity(row.entity)"
                          :key="f"
                          :value="f"
                        >
                          {{ f }}
                        </option>
                      </select>
                    </td>
                    <td class="p-2 border">
                      <select
                        v-model="row.transform"
                        class="border rounded p-1 w-full"
                        :disabled="isCompleted"
                      >
                        <option value="">None</option>
                        <option value="trim">Trim</option>
                        <option value="upper">Uppercase</option>
                        <option value="lower">Lowercase</option>
                      </select>
                    </td>
                    <td class="p-2 border">
                      <select
                        v-model="row.apply_mode"
                        class="border rounded p-1 w-full"
                        :disabled="isCompleted"
                      >
                        <option value="both">Both</option>
                        <option value="insert">Insert only</option>
                        <option value="update">Update only</option>
                      </select>
                    </td>
                    <td class="p-2 border text-center">
                      <input type="checkbox" v-model="row.skip" :disabled="isCompleted" />
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
            <div v-if="mappingSaved" class="text-sm text-emerald-700 mt-2">
              Mappings saved ({{ mappingSavedCount }}).
            </div>
            <div v-else-if="mappingError" class="text-sm text-red-600 mt-2">
              {{ mappingError }}
            </div>
          </div>

          <div v-else-if="!isCompleted" class="pt-4">
            <h3 class="font-semibold mb-2">Detected Columns</h3>
            <p class="text-sm text-gray-600">
              No columns detected.
              {{
                detectedNote ||
                "Preview is available for CSV/TXT files. You can still apply a template or use the saved mappings below."
              }}
            </p>
          </div>

          <div v-if="processResult" class="pt-4">
            <h3 class="font-semibold mb-2">Import Result</h3>
            <pre class="bg-gray-50 border rounded p-3 text-sm overflow-x-auto">{{
              processResult
            }}</pre>
          </div>

          <div class="pt-4">
            <div class="flex items-center justify-between mb-2">
              <h3 class="font-semibold">Logs</h3>
              <div class="flex items-center gap-2 text-sm">
                <label class="text-gray-600">Show</label>
                <select
                  v-model.number="eventsLimit"
                  class="border rounded p-1"
                  @change="fetchEvents"
                >
                  <option :value="50">50</option>
                  <option :value="100">100</option>
                  <option :value="200">200</option>
                  <option :value="500">500</option>
                </select>
                <button
                  @click.prevent="fetchEvents"
                  class="px-2 py-1 border rounded text-sm"
                  :disabled="loadingEvents"
                >
                  {{ loadingEvents ? "Refreshing…" : "Refresh" }}
                </button>
              </div>
            </div>
            <div class="overflow-x-auto">
              <table class="min-w-full border bg-white text-sm">
                <thead>
                  <tr class="bg-gray-50 text-left text-xs uppercase text-gray-600">
                    <th class="p-2 border">Time</th>
                    <th class="p-2 border">Level</th>
                    <th class="p-2 border">Event</th>
                    <th class="p-2 border">Message</th>
                    <th class="p-2 border">Row</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="ev in events" :key="ev.id" class="border-t">
                    <td class="p-2 border whitespace-nowrap">
                      {{ new Date(ev.created_at).toLocaleString() }}
                    </td>
                    <td class="p-2 border">
                      <span
                        :class="[
                          'px-2 py-0.5 rounded text-xs',
                          ev.level === 'error'
                            ? 'bg-red-100 text-red-800'
                            : ev.level === 'warning'
                            ? 'bg-amber-100 text-amber-800'
                            : 'bg-gray-100 text-gray-700',
                        ]"
                        >{{ ev.level }}</span
                      >
                    </td>
                    <td class="p-2 border">{{ ev.event }}</td>
                    <td class="p-2 border">
                      <div>{{ ev.message }}</div>
                      <div v-if="ev.context" class="text-xs text-gray-500">
                        {{ ev.context }}
                      </div>
                    </td>
                    <td class="p-2 border">{{ ev.import_row_id ?? "—" }}</td>
                  </tr>
                  <tr v-if="!events.length">
                    <td class="p-3 text-center text-gray-500" colspan="5">
                      No events yet
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
        </div>
      </div>
    </div>
  </AppLayout>
</template>

<style scoped></style>
