import { api } from '../core/api.js';
import { $ } from '../core/dom.js';
import { normalizeUiText } from '../core/text.js';
import { loadWork } from './batch.js';
import { remapAutocorrectMarkersAfterRenumber } from './dictionary-autocorrect.js';
import { getConfiguredFieldsForActiveDocType, getCurrentProjectKind, getDocTypeFieldContext, resolveFieldConfigRootKind } from './project-context.js';
import { primeOrigInputs } from './suggestions.js';
import { recordOpsState, state } from '../state/app-state.js';
import { commitPendingHistoryForActiveInput, flushAutosaveQueue, pushHistoryAction } from '../state/autosave.js';
import { clearWorkPrefetchCache, cloneRecordSnapshot, findMoveTargetIndex, hideReorderOverlay, invalidateCachedWorkPayload, orderRecordsFromServerIds, recordsBlock, remapRecordIdsAfterServerRenumber, showReorderOverlay } from '../ui/events.js';
import { applyEmptyFilter, buildEmptyRecordFieldsTemplate, buildRecordInitialUpdates, renderRecordsBlock, syncRecordFieldsFromDom } from '../ui/form/field-table.js';
import { focusFirstRecordOrigInput } from '../ui/highlight.js';
import { hideHeaderAddOverlay, hideHeaderDeleteOverlay, hideRecordDeleteOverlay, hideRecordInsertOverlay, showConfirmActionPanel, showHeaderAddOverlay, showHeaderDeleteOverlay, showRecordDeleteOverlay, showRecordInsertOverlay } from '../ui/modals.js';
import { setStatus } from '../ui/status-bar.js';

function invalidateWorkCacheForImage(imageId) {
  // Record add/delete/move/transfer renumber RecordIDs across the WHOLE batch
  // DB (renumber_all_records_and_grids). Since save/autosave targets records by
  // global RecordID, any cached or prefetched payload of another image would now
  // point to shifted RecordIDs. Purge the entire batch cache + pending prefetch
  // to avoid writing edits onto the wrong (renumbered) record.
  if (!state.batch) return;
  invalidateCachedWorkPayload(state.batch, null);
  clearWorkPrefetchCache();
}

function remapIdsAndAutocorrectMarkers(rawMap) {
  const idMap = remapRecordIdsAfterServerRenumber(rawMap);
  if (idMap) remapAutocorrectMarkersAfterRenumber(idMap);
  return idMap;
}

export async function moveRecordById(recordId, direction, pushToHistory) {
  if (!state.batch || !state.work || !Array.isArray(state.work.records) || recordOpsState.inFlight) return false;
  const records = state.work.records;
  const index = records.findIndex((r) => r && r.record_id === recordId);
  if (index < 0) return false;
  const targetIndex = findMoveTargetIndex(index, direction);
  if (targetIndex < 0) return false;

  recordOpsState.inFlight = true;
  showReorderOverlay();
  try {
    const resp = await api(`/api/batch/${encodeURIComponent(state.batch)}/record/move`, {
      method: "POST",
      body: JSON.stringify({ record_id: recordId, direction }),
    });
    if (!resp || resp.moved === false) return false;
    syncRecordFieldsFromDom();
    const idMap = remapIdsAndAutocorrectMarkers(resp.record_id_map);
    const remappedRecordId = idMap && idMap.get(recordId) != null ? idMap.get(recordId) : recordId;
    const moving = records[index];
    records.splice(index, 1);
    records.splice(targetIndex, 0, moving);
    state.work.records = records;
    orderRecordsFromServerIds(resp.ordered_record_ids);
    renderRecordsBlock();
    primeOrigInputs($("recordsBlock"));
    applyEmptyFilter();

    if (pushToHistory) {
      pushHistoryAction({
        kind: "record-move",
        recordId: remappedRecordId,
        moveDirection: direction,
        timestamp: Date.now(),
      });
    }
    invalidateWorkCacheForImage(state.work && state.work.image_id);
    return true;
  } finally {
    hideReorderOverlay();
    recordOpsState.inFlight = false;
  }
}


export async function insertRecordAtIndex(insertIndex, snapshot, pushToHistory) {
  const records = Array.isArray(state.work.records) ? state.work.records : [];
  const headers = state.work && Array.isArray(state.work.headers) ? state.work.headers : [];
  if (!state.batch || !state.work || !headers.length || recordOpsState.inFlight) return false;
  recordOpsState.inFlight = true;
  showRecordInsertOverlay();
  try {
    const idx = Math.max(0, Math.min(insertIndex, records.length));
    let headerIdForAdd;
    if (snapshot && snapshot.header_id != null) {
      headerIdForAdd = snapshot.header_id;
    } else if (idx > 0 && records[idx - 1] && records[idx - 1].header_id != null) {
      headerIdForAdd = records[idx - 1].header_id;
    } else if (idx < records.length && records[idx] && records[idx].header_id != null) {
      headerIdForAdd = records[idx].header_id;
    } else {
      headerIdForAdd = headers[0].header_id;
    }
    let afterRecordId = null;
    let beforeRecordId = null;
    for (let i = idx - 1; i >= 0; i--) {
      if (records[i] && records[i].header_id === headerIdForAdd) {
        afterRecordId = records[i].record_id;
        break;
      }
    }
    if (afterRecordId == null) {
      for (let i = idx; i < records.length; i++) {
        if (records[i] && records[i].header_id === headerIdForAdd) {
          beforeRecordId = records[i].record_id;
          break;
        }
      }
    }
    const body = {
      header_id: headerIdForAdd,
      after_record_id: afterRecordId,
      before_record_id: beforeRecordId,
      initial_updates: buildRecordInitialUpdates(snapshot),
      auto_increment_individual_number: !!state.individualNumberAutoIncrement,
    };
    const resp = await api(`/api/batch/${encodeURIComponent(state.batch)}/record/add`, {
      method: "POST",
      body: JSON.stringify(body),
    });
    syncRecordFieldsFromDom();
    remapIdsAndAutocorrectMarkers(resp && resp.record_id_map);
    const recordId = resp && resp.record_id != null ? resp.record_id : null;
    if (recordId == null) return false;

    const inserted = snapshot
      ? cloneRecordSnapshot(snapshot)
      : {
          record_id: recordId,
          fields: buildEmptyRecordFieldsTemplate(),
          header_id: headerIdForAdd,
        };
    inserted.record_id = recordId;
    if (inserted.header_id == null) inserted.header_id = headerIdForAdd;
    if (!inserted.fields) inserted.fields = {};
    records.splice(idx, 0, inserted);
    state.work.records = records;
    orderRecordsFromServerIds(resp && resp.ordered_record_ids);
    renderRecordsBlock();
    primeOrigInputs($("recordsBlock"));
    applyEmptyFilter();
    requestAnimationFrame(() => {
      focusFirstRecordOrigInput(recordId);
    });

    if (pushToHistory) {
      pushHistoryAction({
        kind: "record-insert",
        insertIndex: idx,
        snapshot: cloneRecordSnapshot(inserted),
        timestamp: Date.now(),
      });
    }
    invalidateWorkCacheForImage(state.work && state.work.image_id);
    return true;
  } finally {
    hideRecordInsertOverlay();
    recordOpsState.inFlight = false;
  }
}


export async function removeRecordAtIndex(removeIndex, pushToHistory) {
  if (!state.batch || !state.work || !Array.isArray(state.work.records) || recordOpsState.inFlight) return false;
  recordOpsState.inFlight = true;
  try {
    const records = state.work.records;
    if (removeIndex < 0 || removeIndex >= records.length) return false;
    const rec = records[removeIndex];
    const resp = await api(`/api/batch/${encodeURIComponent(state.batch)}/record/${encodeURIComponent(rec.record_id)}`, {
      method: "DELETE",
    });
    syncRecordFieldsFromDom();
    const removedSnapshot = cloneRecordSnapshot(rec);
    records.splice(removeIndex, 1);
    remapIdsAndAutocorrectMarkers(resp && resp.record_id_map);
    state.work.records = records;
    orderRecordsFromServerIds(resp && resp.ordered_record_ids);
    renderRecordsBlock();
    primeOrigInputs($("recordsBlock"));
    applyEmptyFilter();

    if (pushToHistory) {
      pushHistoryAction({
        kind: "record-remove",
        removeIndex,
        snapshot: removedSnapshot,
        timestamp: Date.now(),
      });
    }
    invalidateWorkCacheForImage(state.work && state.work.image_id);
    return true;
  } finally {
    recordOpsState.inFlight = false;
  }
}


export async function handleRecordAssignHeader(recordIndex, newHeaderId) {
  if (!state.batch || !state.work || !Array.isArray(state.work.records)) return;
  const rec = state.work.records[recordIndex];
  if (!rec || rec.record_id == null) return;
  if (Number(rec.header_id) === Number(newHeaderId)) return;
  const anchorRecordId = rec.record_id;
  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("assign-header");
  if (!saved) {
    renderRecordsBlock();
    return;
  }
  try {
    const resp = await api(
      `/api/batch/${encodeURIComponent(state.batch)}/record/${encodeURIComponent(
        rec.record_id
      )}/assign-header`,
      {
        method: "POST",
        body: JSON.stringify({ header_id: newHeaderId, propagate_to_following: true }),
      }
    );
    const updatedRecordIds = Array.isArray(resp && resp.updated_record_ids)
      ? new Set(resp.updated_record_ids.map((v) => Number(v)).filter((v) => Number.isFinite(v)))
      : new Set([Number(anchorRecordId)]);
    const records = Array.isArray(state.work.records) ? state.work.records : [];
    records.forEach((row) => {
      if (!row || row.record_id == null) return;
      if (updatedRecordIds.has(Number(row.record_id))) {
        row.header_id = newHeaderId;
      }
    });
    state.work.records = records;
    orderRecordsFromServerIds(resp && resp.ordered_record_ids);
    renderRecordsBlock();
    primeOrigInputs($("recordsBlock"));
    applyEmptyFilter();
    const focusSel = document.querySelector(
      `.record-card[data-record-id="${String(anchorRecordId)}"] .record-header-sel`
    );
    if (focusSel && typeof focusSel.focus === "function") focusSel.focus();
  } catch (e) {
    setStatus(e.message, true);
    renderRecordsBlock();
    return;
  }
}


export async function handleHeaderMoveClick(index, direction) {
  if (!state.batch || !state.work || state.work.image_id == null) return;
  const list = state.work.headers;
  if (!Array.isArray(list) || list.length < 2) return;
  const newOrder = list.slice();
  if (direction === "up") {
    if (index <= 0) return;
    const t = newOrder[index - 1];
    newOrder[index - 1] = newOrder[index];
    newOrder[index] = t;
  } else {
    if (index >= newOrder.length - 1) return;
    const t = newOrder[index + 1];
    newOrder[index + 1] = newOrder[index];
    newOrder[index] = t;
  }
  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("header-reorder");
  if (!saved) return;
  const iid = state.work.image_id;
  showReorderOverlay();
  try {
    setStatus("Réorganisation des Headers...");
    await api(
      `/api/batch/${encodeURIComponent(state.batch)}/image/${iid}/header/reorder`,
      {
        method: "POST",
        body: JSON.stringify({
          header_ids: newOrder.map((h) => h.header_id),
        }),
      }
    );
  } catch (e) {
    setStatus(e.message, true);
    return;
  } finally {
    hideReorderOverlay();
  }
  invalidateWorkCacheForImage(iid);
  await loadWork(iid);
}


export async function handleHeaderDeleteClick(headerId) {
  if (!state.batch || !state.work || state.work.image_id == null) return;
  const n = Array.isArray(state.work.headers) ? state.work.headers.length : 0;
  if (n < 2) {
    setStatus("Impossible de supprimer le seul Header.", true);
    return;
  }
  const ok = await showConfirmActionPanel(
    "Supprimer le Header",
    normalizeUiText("Supprimer ce Header ? Les champs de cette section seront effacés de la base."),
    "Supprimer",
    { kind: "danger" }
  );
  if (!ok) return;
  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("header-delete");
  if (!saved) return;
  const iid = state.work.image_id;
  showHeaderDeleteOverlay();
  try {
    setStatus("Suppression du Header...");
    await api(
      `/api/batch/${encodeURIComponent(state.batch)}/image/${iid}/header/${encodeURIComponent(
        headerId
      )}`,
      { method: "DELETE" }
    );
  } catch (e) {
    setStatus(e.message, true);
    return;
  } finally {
    hideHeaderDeleteOverlay();
  }
  invalidateWorkCacheForImage(iid);
  await loadWork(iid);
}


export async function handleHeaderAddSection(options) {
  if (!state.batch || !state.work || state.work.image_id == null) return false;
  const opts = options || {};
  const headerFormType = String(opts.headerFormType || "").trim();
  const confirmTitle = String(opts.confirmTitle || "Ajouter un Header");
  const confirmMessage = String(
    opts.confirmMessage || "Ajouter une nouvelle section Header pour cette image ?"
  );
  const okLabel = String(opts.okLabel || "Ajouter");
  const statusMessage = String(opts.statusMessage || "Ajout d'un Header...");
  const ok = await showConfirmActionPanel(confirmTitle, confirmMessage, okLabel);
  if (!ok) return false;
  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("add-header-section");
  if (!saved) return false;
  const iid = state.work.image_id;
  showHeaderAddOverlay();
  try {
    setStatus(statusMessage);
    const ctx = getDocTypeFieldContext();
    const allowedHeaderFields = Array.isArray(ctx && ctx.fields)
      ? ctx.fields
          .map((f) => String((f && f.fieldName) || "").trim())
          .filter((name) => !!name)
      : [];
    const censusHeaderFields = getConfiguredFieldsForActiveDocType(
      getCurrentProjectKind(),
      "Census"
    )
      .map((f) => String((f && f.fieldName) || "").trim())
      .filter(Boolean);
    const coverpageHeaderFields = getConfiguredFieldsForActiveDocType(
      getCurrentProjectKind(),
      "Coverpage"
    )
      .map((f) => String((f && f.fieldName) || "").trim())
      .filter(Boolean);
    const payload = {
      allowed_header_fields: allowedHeaderFields,
      census_header_fields: censusHeaderFields,
      coverpage_header_fields: coverpageHeaderFields,
    };
    if (headerFormType) payload.header_form_type = headerFormType;
    await api(
      `/api/batch/${encodeURIComponent(state.batch)}/image/${iid}/header/add`,
      {
        method: "POST",
        body: JSON.stringify(payload),
      }
    );
  } catch (e) {
    setStatus(e.message, true);
    return false;
  } finally {
    hideHeaderAddOverlay();
  }
  invalidateWorkCacheForImage(iid);
  await loadWork(iid);
  return true;
}


export async function handleHeaderFormTypeChange(headerId, nextValue, selectEl) {
  if (!state.batch || !state.work || state.work.image_id == null) return false;
  const iid = state.work.image_id;
  const formType = String(nextValue == null ? "" : nextValue).trim();
  if (!formType) return false;
  const previousValue = selectEl ? String(selectEl.dataset.prevValue || "") : "";
  if (formType === previousValue) return true;

  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("header-formtype-change");
  if (!saved) {
    if (selectEl) selectEl.value = previousValue || formType;
    return false;
  }

  try {
    setStatus("Mise à jour du Header Event Type...");
    await api(
      `/api/batch/${encodeURIComponent(state.batch)}/image/${iid}/header/${encodeURIComponent(
        headerId
      )}/form-type`,
      {
        method: "POST",
        body: JSON.stringify({ form_type: formType }),
      }
    );
    if (selectEl) selectEl.dataset.prevValue = formType;
  } catch (e) {
    if (selectEl) selectEl.value = previousValue || formType;
    setStatus(e.message, true);
    return false;
  }
  invalidateWorkCacheForImage(iid);
  await loadWork(iid);
  return true;
}


export async function handleRecordAddClick(insertIndex) {
  const ok = await showConfirmActionPanel(
    "Ajouter un Record",
    "Ajouter un nouveau record \u00e0 cette position ?",
    "Ajouter"
  );
  if (!ok) return;
  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("record-add");
  if (!saved) return;
  try {
    const okInsert = await insertRecordAtIndex(insertIndex, null, true);
    if (!okInsert) {
      setStatus("Impossible d'ajouter le record.", true);
    }
  } catch (e) {
    setStatus((e && e.message) || "Impossible d'ajouter le record.", true);
  }
}


export async function handleRecordRemoveClick(removeIndex) {
  if (!state.work || !Array.isArray(state.work.records)) return;
  if (removeIndex < 0 || removeIndex >= state.work.records.length) return;
  const displayId = removeIndex + 1;
  const ok = await showConfirmActionPanel(
    "Supprimer le Record",
    normalizeUiText(
      "Supprimer le RecordID " +
        displayId +
        " ?\nTous les RecordID du lot seront renumérotés. Cette action sera ajoutée dans l'historique Annuler/Rétablir."
    ),
    "Supprimer",
    { kind: "danger" }
  );
  if (!ok) return;

  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("record-remove");
  if (!saved) return;
  showRecordDeleteOverlay();
  try {
    await removeRecordAtIndex(removeIndex, true);
  } finally {
    hideRecordDeleteOverlay();
  }
}


export async function handleRecordMoveClick(recordIndex, direction) {
  if (!state.work || !Array.isArray(state.work.records)) return;
  if (recordIndex < 0 || recordIndex >= state.work.records.length) return;
  const rec = state.work.records[recordIndex];
  if (!rec || rec.record_id == null) return;

  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue("record-move");
  if (!saved) return;
  await moveRecordById(rec.record_id, direction, true);
}


export async function handleRecordTransferToPrevImage(recordIndex, mode) {
  if (!state.batch || !state.work || !Array.isArray(state.work.records) || recordOpsState.inFlight) return;
  if (recordIndex !== 0 || state.imageIndex <= 0) return;
  if (resolveFieldConfigRootKind(getCurrentProjectKind()) !== "BDM") return;
  const rec = state.work.records[recordIndex];
  if (!rec || rec.record_id == null) return;

  const transferMode = mode === "merge" ? "merge" : "move";
  const confirmTitle = transferMode === "merge" ? "Fusionner avec l'image précédente" : "Déplacer vers l'image précédente";
  const confirmMessage =
    transferMode === "merge"
      ? "Les données de ce record seront fusionnées dans le dernier record de l'image précédente. Ce record sera ensuite supprimé de l'image actuelle."
      : "Ce record sera transféré sans fusion comme dernier record de l'image précédente. Il sera retiré de l'image actuelle.";
  const okLabel = transferMode === "merge" ? "Fusionner" : "Déplacer";
  const ok = await showConfirmActionPanel(
    confirmTitle,
    normalizeUiText(confirmMessage),
    okLabel,
    { kind: "danger" }
  );
  if (!ok) return;

  commitPendingHistoryForActiveInput();
  const saved = await flushAutosaveQueue(transferMode === "merge" ? "record-merge-prev-image" : "record-transfer-prev-image");
  if (!saved) return;

  const imageId = state.work.image_id;
  recordOpsState.inFlight = true;
  showReorderOverlay();
  try {
    const resp = await api(
      `/api/batch/${encodeURIComponent(state.batch)}/record/${encodeURIComponent(rec.record_id)}/transfer-to-previous-image`,
      {
        method: "POST",
        body: JSON.stringify({ mode: transferMode }),
      }
    );
    remapIdsAndAutocorrectMarkers(resp && resp.record_id_map);
    invalidateWorkCacheForImage(imageId);
    if (state.imageIndex > 0 && state.images[state.imageIndex - 1]) {
      invalidateWorkCacheForImage(state.images[state.imageIndex - 1].image_id);
    }
    await loadWork(imageId);
    setStatus(transferMode === "merge" ? "Record fusionné avec l'image précédente." : "Record déplacé vers l'image précédente.");
  } catch (e) {
    setStatus((e && e.message) || "Impossible de transférer le record.", true);
  } finally {
    hideReorderOverlay();
    recordOpsState.inFlight = false;
  }
}
