import { AUTOSAVE_DEBOUNCE_MS } from '../config/constants.js';
import { $ } from '../core/dom.js';
import { api } from '../core/api.js';
import { updateCurrentImageDocType } from '../services/batch.js';
import { filterPersistableAutosaveUpdates, isOrigInputPersistable } from '../services/project-context.js';
import { insertRecordAtIndex, moveRecordById, removeRecordAtIndex } from '../services/records.js';
import { autosaveState, historyState, state, runtime } from './app-state.js';
import { mergePendingPayload, invalidateCachedWorkPayload } from '../ui/events.js';
import { setStatus } from '../ui/status-bar.js';

export function resetAutosaveState() {
  if (autosaveState.timer) {
    clearTimeout(autosaveState.timer);
    autosaveState.timer = null;
  }
  autosaveState.inFlight = false;
  autosaveState.needsFlush = false;
  autosaveState.pendingByEntity.clear();
}


export function resetHistoryState() {
  historyState.applying = false;
  historyState.undoStack = [];
  historyState.redoStack = [];
  historyState.pendingEdits.clear();
  updateUndoRedoButtons();
}


export function updateUndoRedoButtons() {
  const undoBtn = $("btnUndo");
  const redoBtn = $("btnRedo");
  if (undoBtn) undoBtn.disabled = historyState.undoStack.length === 0;
  if (redoBtn) redoBtn.disabled = historyState.redoStack.length === 0;
}


export function getEntityRefFromInput(input) {
  if (!input) return null;
  const row = input.closest("tr.field-table__row");
  if (!row) return null;
  const recordIdRaw = row.dataset.recordId;
  if (recordIdRaw != null && recordIdRaw !== "") {
    const recordId = parseInt(recordIdRaw, 10);
    if (Number.isFinite(recordId)) {
      return { entityKey: `record:${recordId}`, recordId };
    }
  }
  const headerIdRaw = row.dataset.headerId || (state.work && state.work.header_id != null ? String(state.work.header_id) : "");
  const headerId = parseInt(headerIdRaw, 10);
  if (Number.isFinite(headerId)) {
    return { entityKey: `header:${headerId}`, headerId };
  }
  return null;
}


export function findOrigInputForEntity(entityKey, column) {
  if (!entityKey || !column) return null;
  if (entityKey.startsWith("record:")) {
    const recordId = entityKey.slice("record:".length);
    return document.querySelector(`.record-card[data-record-id="${recordId}"] .field-table__input[data-col="${column}"]`);
  }
  if (entityKey.startsWith("header:")) {
    const headerId = entityKey.slice("header:".length);
    return document.querySelector(
      `#headerFields .header-section[data-header-id="${headerId}"] .field-table__input[data-col="${column}"]`
    );
  }
  return null;
}


export function queueAutosaveForInput(input, immediate) {
  if (!input || input.readOnly) return;
  if (!isOrigInputPersistable(input)) return;
  const col = String(input.dataset.col || "");
  if (!col.endsWith("_orig")) return;
  const entity = getEntityRefFromInput(input);
  if (!entity) return;

  const pending = autosaveState.pendingByEntity.get(entity.entityKey) || {
    entity,
    updates: {},
  };
  pending.updates[col] = input.value;
  autosaveState.pendingByEntity.set(entity.entityKey, pending);

  if (autosaveState.timer) {
    clearTimeout(autosaveState.timer);
    autosaveState.timer = null;
  }

  if (immediate) {
    void flushAutosaveQueue("immediate");
    return;
  }

  setStatus("Modifications en attente...");
  autosaveState.timer = setTimeout(() => {
    autosaveState.timer = null;
    void flushAutosaveQueue("debounced");
  }, AUTOSAVE_DEBOUNCE_MS);
}


export function pushHistoryAction(action) {
  historyState.undoStack.push(action);
  historyState.redoStack = [];
  updateUndoRedoButtons();
}


export function commitHistoryActionForInput(input) {
  if (!input) return;
  const pending = historyState.pendingEdits.get(input);
  if (!pending) return;
  historyState.pendingEdits.delete(input);
  if (historyState.applying) return;
  const entity = getEntityRefFromInput(input);
  if (!entity) return;
  const column = String(input.dataset.col || "");
  const oldValue = pending.startValue;
  const newValue = input.value;
  if (oldValue === newValue) return;
  pushHistoryAction({
    kind: "field-edit",
    entityKey: entity.entityKey,
    column,
    oldValue,
    newValue,
    timestamp: Date.now(),
  });
}


export function commitPendingHistoryForActiveInput() {
  const active = runtime.activeEditInput;
  if (active && active.matches && active.matches(".field-table__input[data-col$='_orig']")) {
    commitHistoryActionForInput(active);
    return;
  }
  const focused = document.activeElement;
  if (focused && focused.matches && focused.matches(".field-table__input[data-col$='_orig']")) {
    commitHistoryActionForInput(focused);
  }
}


export async function applyHistoryStep(direction) {
  const source = direction === "undo" ? historyState.undoStack : historyState.redoStack;
  if (!source.length) return;
  const action = source.pop();
  const target = direction === "undo" ? historyState.redoStack : historyState.undoStack;
  const kind = action && action.kind ? action.kind : "field-edit";
  try {
    if (kind === "field-edit") {
      const nextValue = direction === "undo" ? action.oldValue : action.newValue;
      const input = findOrigInputForEntity(action.entityKey, action.column);
      if (!input) {
        updateUndoRedoButtons();
        return;
      }
      historyState.applying = true;
      try {
        input.value = nextValue;
        input.dispatchEvent(new Event("input", { bubbles: true }));
      } finally {
        historyState.applying = false;
      }
    } else if (kind === "record-insert") {
      const saved = await flushAutosaveQueue("history-structure");
      if (!saved) throw new Error("Impossible d'appliquer l'historique (autosave).");
      if (direction === "undo") {
        const ok = await removeRecordAtIndex(action.insertIndex, false);
        if (!ok) throw new Error("Impossible d'annuler l'ajout du record.");
      } else {
        const ok = await insertRecordAtIndex(action.insertIndex, action.snapshot || null, false);
        if (!ok) throw new Error("Impossible de r\u00e9tablir l'ajout du record.");
      }
    } else if (kind === "record-remove") {
      const saved = await flushAutosaveQueue("history-structure");
      if (!saved) throw new Error("Impossible d'appliquer l'historique (autosave).");
      if (direction === "undo") {
        const ok = await insertRecordAtIndex(action.removeIndex, action.snapshot || null, false);
        if (!ok) throw new Error("Impossible d'annuler la suppression du record.");
      } else {
        const ok = await removeRecordAtIndex(action.removeIndex, false);
        if (!ok) throw new Error("Impossible de r\u00e9tablir la suppression du record.");
      }
    } else if (kind === "record-move") {
      const saved = await flushAutosaveQueue("history-structure");
      if (!saved) throw new Error("Impossible d'appliquer l'historique (autosave).");
      const moveDirection = direction === "undo"
        ? (action.moveDirection === "up" ? "down" : "up")
        : action.moveDirection;
      const ok = await moveRecordById(action.recordId, moveDirection, false);
        if (!ok) throw new Error("Impossible d'appliquer le d\u00e9placement du record.");
    } else if (kind === "doc-type-change") {
      const saved = await flushAutosaveQueue("history-structure");
      if (!saved) throw new Error("Impossible d'appliquer l'historique (autosave).");
      if (!state.work || state.work.image_id == null) {
        throw new Error("Aucune image active pour appliquer l'historique DocType.");
      }
      if (state.work.image_id !== action.imageId) {
        throw new Error("Ouvrez l'image concern\u00e9e avant d'annuler/r\u00e9tablir ce DocType.");
      }
      const nextDocType = direction === "undo" ? action.oldDocType : action.newDocType;
      await updateCurrentImageDocType(nextDocType, {
        requireConfirm: false,
        pushToHistory: false,
      });
    } else {
      updateUndoRedoButtons();
      return;
    }

    target.push(action);
    updateUndoRedoButtons();
  } catch (err) {
    source.push(action);
    updateUndoRedoButtons();
    setStatus((err && err.message) || "Erreur lors de l'historique", true);
  }
}


export async function undoLastEdit() {
  commitPendingHistoryForActiveInput();
  await applyHistoryStep("undo");
}


export async function redoLastEdit() {
  commitPendingHistoryForActiveInput();
  await applyHistoryStep("redo");
}


export async function flushAutosaveQueue(reason) {
  if (!state.batch) return true;
  if (autosaveState.inFlight) {
    autosaveState.needsFlush = true;
    return true;
  }
  if (!autosaveState.pendingByEntity.size) return true;

  if (autosaveState.timer) {
    clearTimeout(autosaveState.timer);
    autosaveState.timer = null;
  }

  const payloads = Array.from(autosaveState.pendingByEntity.entries())
    .map(([entityKey, payload]) => {
      const updates = filterPersistableAutosaveUpdates(payload.entity, payload.updates);
      return { entityKey, entity: payload.entity, updates };
    })
    .filter((item) => Object.keys(item.updates).length > 0);
  autosaveState.pendingByEntity.clear();
  if (!payloads.length) return true;

  autosaveState.inFlight = true;
  setStatus("Enregistrement automatique...");

  try {
    for (const item of payloads) {
      const body = { updates: item.updates };
      if (item.entity.recordId != null) {
        body.record_id = item.entity.recordId;
      } else if (item.entity.headerId != null) {
        body.header_id = item.entity.headerId;
      } else {
        continue;
      }
      await api(`/api/batch/${encodeURIComponent(state.batch)}/save`, {
        method: "POST",
        body: JSON.stringify(body),
      });
    }
    setStatus("Enregistré automatiquement");
    if (state.work && state.work.image_id != null) {
      invalidateCachedWorkPayload(state.batch, state.work.image_id);
    }
    return true;
  } catch (e) {
    payloads.forEach((item) => {
      mergePendingPayload(item.entityKey, {
        entity: item.entity,
        updates: item.updates,
      });
    });
    setStatus("Erreur d'enregistrement automatique: " + e.message, true);
    return false;
  } finally {
    autosaveState.inFlight = false;
    if (autosaveState.needsFlush) {
      autosaveState.needsFlush = false;
      autosaveState.timer = setTimeout(() => {
        autosaveState.timer = null;
        void flushAutosaveQueue("queued");
      }, 0);
    } else if (autosaveState.pendingByEntity.size && reason !== "beforeunload") {
      autosaveState.timer = setTimeout(() => {
        autosaveState.timer = null;
        void flushAutosaveQueue("retry");
      }, AUTOSAVE_DEBOUNCE_MS);
    }
  }
}


export async function flushAutosaveBeforeNavigation(reason) {
  commitPendingHistoryForActiveInput();
  if (autosaveState.timer) {
    clearTimeout(autosaveState.timer);
    autosaveState.timer = null;
  }
  while (autosaveState.inFlight) {
    await new Promise((resolve) => setTimeout(resolve, 25));
  }
  return await flushAutosaveQueue(reason);
}
