const DetailPenjemputan = (function () {
  "use strict";

    const CONFIG = {
        OCR_AREAS: [
            { id: 'A', x: 0.34, y: 0.35, w: 0.40, h: 0.11, color: 'border-lime-400 bg-lime-500/15' },
            { id: 'B', x: 0.31, y: 0.33, w: 0.45, h: 0.14, color: 'border-amber-300 bg-amber-400/10' },
            { id: 'C', x: 0.29, y: 0.31, w: 0.49, h: 0.17, color: 'border-cyan-300 bg-cyan-400/10' }
        ],
        JENIS_SAMPAH: ['Organik', 'Anorganik', 'Residu'],
        DEFAULT_JENIS: 'Residu'
    };

    const ENDPOINTS = {
        saveRecord: '/upst/detail-penjemputan/save-record',
      recordsList: '/upst/detail-penjemputan/api/records',
        uploadKedatangan: '/upst/detail-penjemputan/upload-foto-kedatangan',
        uploadTimbangan: '/upst/detail-penjemputan/upload-foto-timbangan',
        uploadPetugas: '/upst/detail-penjemputan/upload-foto-petugas',
        submit: '/upst/detail-penjemputan'
    };

  let state = {
    activeTpsIndex: 0,
    tpsData: [],
    availableTpsList: [],
    selectedTpsList: [],
    hasRequestedLocation: [],
    nomorSpj: "SPJ/07-2025/PKM/000476",
  };

  const STORAGE_KEY = "detailPenjemputanTpsState";
  function sanitizeStorageSegment(value) {
    return String(value || "default")
      .trim()
      .replace(/[^a-zA-Z0-9_-]/g, "-")
      .replace(/-+/g, "-")
      .replace(/^-|-$/g, "") || "default";
  }

  function getStorageKey(nomorSpj = state.nomorSpj) {
    return `${STORAGE_KEY}:${sanitizeStorageSegment(nomorSpj)}`;
  }

  function saveState() {
    return;
  }

  function loadState(nomorSpj = state.nomorSpj) {
    return;
  }

  function clearState() {
    return;
  }

  function getTpsIdentity(value) {
    if (!value) return "";
    const spjDetailId = value.spjDetailId || value.SpjDetailID || "";
    const lokasiAngkutId = value.lokasiAngkutId || value.LokasiAngkutID || "";
    const name = value.name || value.Name || "";
    return [spjDetailId, lokasiAngkutId, name].filter(Boolean).join("::");
  }

  function getPersistedUiState() {
    return null;
  }

  function applyPersistedTpsData(persistedUiState) {
    return;
  }

  function applyPersistedUiState(persistedUiState) {
    return;
  }

  function isBrowserFile(value) {
    return typeof File !== "undefined" && value instanceof File;
  }

  function getFirstValue(value) {
    return Array.isArray(value) ? value[0] : value;
  }

  function resolveStoredPhoto(value) {
    if (!value) {
      return null;
    }

    if (isBrowserFile(value)) {
      return value;
    }

    if (typeof value === "string") {
      return {
        url: value,
        name: value.split("/").pop() || "Foto",
        size: 0,
        source: "api",
      };
    }

    if (typeof value !== "object") {
      return null;
    }

    const url =
      value.url ||
      value.fileUrl ||
      value.path ||
      value.filePath ||
      value.src ||
      value.fotoUrl ||
      value.photoUrl ||
      value.fotoFileName ||
      value.fileName ||
      "";

    return {
      ...value,
      url,
      name:
        value.name ||
        value.fileName ||
        value.file_name ||
        (url ? url.split("/").pop() : "Foto"),
      size: Number(value.size || value.fileSize || 0) || 0,
      source: value.source || "api",
    };
  }

  function getStoredPhotoUrl(value) {
    const photo = resolveStoredPhoto(value);
    if (!photo) {
      return "";
    }

    if (isBrowserFile(photo)) {
      return URL.createObjectURL(photo);
    }

    return photo.url || "";
  }

  function getStoredPhotoName(value, fallbackName = "Foto") {
    const photo = resolveStoredPhoto(value);
    if (!photo) {
      return fallbackName;
    }

    return isBrowserFile(photo)
      ? photo.name || fallbackName
      : photo.name || fallbackName;
  }

  function getStoredPhotoSize(value) {
    const photo = resolveStoredPhoto(value);
    if (!photo) {
      return 0;
    }

    return isBrowserFile(photo)
      ? photo.size || 0
      : Number(photo.size || 0) || 0;
  }

  function hasStoredPhoto(value) {
    const photo = resolveStoredPhoto(value);
    if (!photo) {
      return false;
    }

    return isBrowserFile(photo) || Boolean(photo.url || photo.name);
  }

  function normalizePhotoList(value) {
    if (!Array.isArray(value)) {
      return [];
    }

    return value.map(resolveStoredPhoto).filter(Boolean);
  }

  function normalizeTimbanganItem(item) {
    if (!item) {
      return {
        file: null,
        weight: 0,
        jenisSampah: CONFIG.DEFAULT_JENIS,
        uploaded: false,
        ocrInfo: "OCR: belum diproses.",
      };
    }

    const file = resolveStoredPhoto(
      item.file ||
        item.foto ||
        item.photo ||
        item.fotoUrl ||
        item.photoUrl ||
        item.fotoFileName,
    );
    const weight = Number(item.weight ?? getFirstValue(item.berat) ?? 0) || 0;
    const jenisSampah =
      getFirstValue(item.jenisSampah) ||
      item.JenisSampah ||
      CONFIG.DEFAULT_JENIS;
    const uploaded = Boolean(
      item.uploaded ??
      item.isUploaded ??
      item.IsUploaded ??
      hasStoredPhoto(file),
    );

    return {
      ...item,
      file,
      weight,
      jenisSampah,
      uploaded,
      ocrInfo:
        item.ocrInfo ||
        item.OcrInfo ||
        (uploaded ? "Foto dari server." : "OCR: belum diproses."),
    };
  }

  function findMatchingApiTps(apiList, currentTps) {

    return apiList.find(
      (item) =>
        (currentTps.spjDetailId &&
          (item.spjDetailId === currentTps.spjDetailId ||
            item.SpjDetailID === currentTps.spjDetailId)) ||
        (currentTps.lokasiAngkutId &&
          (item.lokasiAngkutId === currentTps.lokasiAngkutId ||
            item.LokasiAngkutID === currentTps.lokasiAngkutId)) ||
        (item.name || item.Name) === currentTps.name,
    ) || null;
  }

  function applyApiRecordData(recordData, { skipRender = false } = {}) {
    const apiList = Array.isArray(recordData)
      ? recordData
      : recordData?.tpsData || [];

    if (!Array.isArray(apiList) || apiList.length === 0) {
      return;
    }

    if (!state.tpsData.length) {
      initializeTpsData(apiList);
    }

    state.tpsData = state.tpsData.map((currentTps, index) => {
      const apiTps = findMatchingApiTps(apiList, currentTps);
      if (!apiTps) {
        return currentTps;
      }

      const fotoKedatangan = normalizePhotoList(
        apiTps.fotoKedatangan || apiTps.FotoKedatangan,
      );
      const fotoPetugas = normalizePhotoList(
        apiTps.fotoPetugas || apiTps.FotoPetugas,
      );
      const apiTimbangan = apiTps.timbangan || apiTps.Timbangan;
      const timbangan = Array.isArray(apiTimbangan)
        ? apiTimbangan.map(normalizeTimbanganItem)
        : currentTps.timbangan;

      return {
        ...currentTps,
        nomorSpj: apiTps.nomorSpj || apiTps.NomorSpj || currentTps.nomorSpj,
        name: apiTps.name || apiTps.Name || currentTps.name,
        lokasiAngkutId:
          apiTps.lokasiAngkutId ||
          apiTps.LokasiAngkutID ||
          currentTps.lokasiAngkutId,
        spjDetailId:
          apiTps.spjDetailId || apiTps.SpjDetailID || currentTps.spjDetailId,
        latitude: apiTps.latitude || apiTps.Latitude || currentTps.latitude,
        longitude: apiTps.longitude || apiTps.Longitude || currentTps.longitude,
        alamatJalan:
          apiTps.alamatJalan || apiTps.AlamatJalan || currentTps.alamatJalan,
        waktuKedatangan:
          apiTps.waktuKedatangan ||
          apiTps.WaktuKedatangan ||
          currentTps.waktuKedatangan,
        fotoKedatangan: fotoKedatangan.length
          ? fotoKedatangan
          : currentTps.fotoKedatangan,
        fotoKedatanganUploaded:
          Boolean(
            apiTps.fotoKedatanganUploaded ?? apiTps.FotoKedatanganUploaded,
          ) ||
          fotoKedatangan.length > 0 ||
          currentTps.fotoKedatanganUploaded,
        timbangan,
        totalOrganik:
          Number(
            apiTps.totalOrganik ??
              apiTps.TotalOrganik ??
              currentTps.totalOrganik,
          ) || 0,
        totalAnorganik:
          Number(
            apiTps.totalAnorganik ??
              apiTps.TotalAnorganik ??
              currentTps.totalAnorganik,
          ) || 0,
        totalResidu:
          Number(
            apiTps.totalResidu ?? apiTps.TotalResidu ?? currentTps.totalResidu,
          ) || 0,
        totalTimbangan:
          Number(
            apiTps.totalTimbangan ??
              apiTps.TotalTimbangan ??
              currentTps.totalTimbangan,
          ) || 0,
        fotoPetugas: fotoPetugas.length ? fotoPetugas : currentTps.fotoPetugas,
        fotoPetugasUploaded:
          Boolean(apiTps.fotoPetugasUploaded ?? apiTps.FotoPetugasUploaded) ||
          fotoPetugas.length > 0 ||
          currentTps.fotoPetugasUploaded,
        namaPetugas:
          apiTps.namaPetugas || apiTps.NamaPetugas || currentTps.namaPetugas,
        submitted: Boolean(
          apiTps.isSubmit ?? apiTps.IsSubmit ?? apiTps.submitted ?? apiTps.Submitted ?? currentTps.submitted,
        ),
        submittedAt:
          apiTps.submittedAt || apiTps.SubmittedAt || currentTps.submittedAt,
      };
    });

    state.selectedTpsList = state.tpsData.map((item) => item.name);
    state.activeTpsIndex = Math.min(
      state.activeTpsIndex,
      Math.max(state.tpsData.length - 1, 0),
    );
    elements.tpsTabsContainer.style.display = "block";

    if (!skipRender) {
      if (state.tpsData.length === 1) {
        renderSingleForm();
      } else {
        renderTabs();
        renderTpsForm();
      }
    }

    updateAllTotals();
    saveState();
  }

  async function loadRecordsForCurrentSpj() {
    if (!state.nomorSpj) return;

    try {
      const fetchRecords = async () => {
        const url = new URL(ENDPOINTS.recordsList, window.location.origin);
        url.searchParams.set('nomorSpj', state.nomorSpj);
        url.searchParams.set('_ts', Date.now().toString());

        const res = await fetch(url.toString(), {
          cache: 'no-store',
          headers: {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            Pragma: 'no-cache',
          },
        });

        if (!res.ok) {
          return null;
        }

        return res.json();
      };

      let data = await fetchRecords();
      if (
        data?.success &&
        Array.isArray(data.items) &&
        data.items.length === 0
      ) {
        await new Promise((resolve) => setTimeout(resolve, 250));
        data = await fetchRecords();
      }

      if (!data?.success || !Array.isArray(data.items) || data.items.length === 0) {
        return;
      }

      applyApiRecordData(data.items, { skipRender: true });
    } catch (error) {
      console.warn('Gagal memuat data TPS:', error);
    }
  }

  const elements = {
    grandTotalDisplay: null,
    tpsSelectionContainer: null,
    tpsTabsContainer: null,
    tpsCheckboxesContainer: null,
    btnConfirmTps: null,
    tpsTabsEl: null,
    tpsContentContainer: null,
    totalOrganikDisplay: null,
    totalAnorganikDisplay: null,
    totalResiduDisplay: null,
  };

    let autoSaveTimer = null;
    let autoSaveStatusEl = null;
  let loadingOverlayEl = null;
  let isAutoSaving = false;
  let pendingAutoSaveIndex = null;
  let lastAutoSaveSignature = "";

  function getLoadingOverlay() {
    if (loadingOverlayEl && document.body.contains(loadingOverlayEl)) {
      return loadingOverlayEl;
    }

    loadingOverlayEl = document.createElement('div');
    loadingOverlayEl.id = 'detail-loading-overlay';
    loadingOverlayEl.className = 'fixed inset-0 z-[9999] hidden bg-white/95 backdrop-blur-sm flex items-center justify-center px-6';
    loadingOverlayEl.innerHTML = `
      <div class="w-full max-w-sm rounded-3xl border border-gray-200 bg-white shadow-2xl px-6 py-7 text-center">
        <div class="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-upst"></div>
        <h2 class="text-base font-black text-gray-800">Sedang memuat data</h2>
        <p class="mt-2 text-sm text-gray-500">Mohon tunggu sebentar, data penjemputan sedang dipulihkan.</p>
      </div>
    `;
    document.body.appendChild(loadingOverlayEl);
    return loadingOverlayEl;
  }

  function showLoadingOverlay() {
    const overlay = getLoadingOverlay();
    overlay.classList.remove('hidden');
    document.body.style.overflow = 'hidden';
  }

  function hideLoadingOverlay() {
    const overlay = getLoadingOverlay();
    overlay.classList.add('hidden');
    document.body.style.overflow = '';
  }

    async function init(tpsList, nomorSpj = state.nomorSpj) {
      state.nomorSpj = nomorSpj || state.nomorSpj;
    showLoadingOverlay();
    try {
      initElements();
      await initializeLocation(tpsList);
    } finally {
      hideLoadingOverlay();
    }
    }

  function initElements() {
    elements.grandTotalDisplay = document.getElementById(
      "grand-total-timbangan",
    );
    elements.tpsSelectionContainer = document.getElementById(
      "tps-selection-container",
    );
    elements.tpsTabsContainer = document.getElementById("tps-tabs-container");
    elements.tpsCheckboxesContainer = document.getElementById("tps-checkboxes");
    elements.btnConfirmTps = document.getElementById("btn-confirm-tps");
    elements.tpsTabsEl = document.getElementById("tps-tabs");
    elements.tpsContentContainer = document.getElementById("tps-content");
    elements.totalOrganikDisplay = document.getElementById(
      "grand-total-organik",
    );
    elements.totalAnorganikDisplay = document.getElementById(
      "grand-total-anorganik",
    );
    elements.totalResiduDisplay = document.getElementById("grand-total-residu");

    if (elements.btnConfirmTps) {
      elements.btnConfirmTps.addEventListener("click", handleConfirmTps);
    }
  }

    function getActiveTps() {
        return state.tpsData[state.activeTpsIndex] || null;
    }

    function getAutoSaveStatusEl() {
        if (!autoSaveStatusEl || !document.body.contains(autoSaveStatusEl)) {
            autoSaveStatusEl = document.getElementById('auto-save-status');
        }
        return autoSaveStatusEl;
    }

    function showAutoSaveStatus(msg, isOk = false) {
        const statusEl = getAutoSaveStatusEl();
        if (!statusEl) return;
        statusEl.textContent = msg;
        statusEl.className = isOk
            ? 'text-[11px] text-green-600 text-center font-medium transition-opacity'
            : 'text-[11px] text-amber-500 text-center font-medium transition-opacity';
        statusEl.style.opacity = '1';
        if (isOk) setTimeout(() => { statusEl.style.opacity = '0'; }, 2500);
    }

    function scheduleAutoSave(tpsIndex = state.activeTpsIndex) {
        clearTimeout(autoSaveTimer);
        showAutoSaveStatus('menyimpan...');
      autoSaveTimer = setTimeout(() => autoSaveRecord(tpsIndex), 1000);
    }

    function buildAutoSavePayload(tps) {
      return {
        nomorSpj: state.nomorSpj || '',
        namaTps: tps.name || '',
        lokasiAngkutId: tps.lokasiAngkutId || '',
        spjDetailId: tps.spjDetailId || '',
        latitude: tps.latitude || '',
        longitude: tps.longitude || '',
        alamatJalan: tps.alamatJalan || '',
        waktuKedatangan: tps.waktuKedatangan || '',
        fotoKedatanganFileNames: tps.fotoKedatanganFileNames || [],
        fotoKedatanganUploaded: tps.fotoKedatanganUploaded || false,
        timbangan: (tps.timbangan || []).map(item => ({
          berat: item.weight || 0,
          jenisSampah: item.jenisSampah || CONFIG.DEFAULT_JENIS,
          fotoFileName: item.fotoFileName || '',
          uploaded: item.uploaded || false,
          ocrInfo: item.ocrInfo || ''
        })),
        totalOrganik: tps.totalOrganik || 0,
        totalAnorganik: tps.totalAnorganik || 0,
        totalResidu: tps.totalResidu || 0,
        totalTimbangan: tps.totalTimbangan || 0,
        fotoPetugasFileNames: tps.fotoPetugasFileNames || [],
        fotoPetugasUploaded: tps.fotoPetugasUploaded || false,
        namaPetugas: tps.namaPetugas || ''
      };
    }

    async function autoSaveRecord(tpsIndex = state.activeTpsIndex) {
      const tps = state.tpsData[tpsIndex] || null;
      if (!tps) return;
      if (tps.submitted) return;
      if (isAutoSaving) {
        pendingAutoSaveIndex = tpsIndex;
        return;
      }

        const payload = buildAutoSavePayload(tps);
        const payloadSignature = JSON.stringify(payload);
        if (payloadSignature === lastAutoSaveSignature) {
            showAutoSaveStatus('✓ Data tersimpan', true);
            return;
        }

        try {
            isAutoSaving = true;
            const res = await fetch(ENDPOINTS.saveRecord, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: payloadSignature
            });
            const data = await res.json();
            if (data.success) {
                lastAutoSaveSignature = payloadSignature;
            }
            showAutoSaveStatus(data.success ? '✓ Data tersimpan' : '✗ Gagal simpan', data.success);
        } catch (_) {
            showAutoSaveStatus('✗ Gagal simpan data');
        } finally {
            isAutoSaving = false;
            if (pendingAutoSaveIndex !== null) {
                const nextIndex = pendingAutoSaveIndex;
                pendingAutoSaveIndex = null;
                scheduleAutoSave(nextIndex);
            }
        }
    }

    async function initializeLocation(tpsList) {
      const persistedUiState = getPersistedUiState();
        state.availableTpsList = tpsList || [];
        if (elements.tpsSelectionContainer) {
            elements.tpsSelectionContainer.style.display = 'none';
        }
        
        if (state.availableTpsList.length === 0) {
            state.selectedTpsList = ['1 Lokasi TPS'];
            initializeTpsData(state.selectedTpsList);
          await loadRecordsForCurrentSpj();
            elements.tpsTabsContainer.style.display = 'block';
            renderSingleForm();
            return;
        }

        state.selectedTpsList = [...state.availableTpsList];
        initializeTpsData(state.selectedTpsList);
        await loadRecordsForCurrentSpj();
        applyPersistedTpsData(persistedUiState);
        applyPersistedUiState(persistedUiState);
        elements.tpsTabsContainer.style.display = 'block';

    if (state.selectedTpsList.length === 1) {
      renderSingleForm();
    } else {
      renderTabs();
      renderTpsForm();
    }
  }

    function initializeTpsData(tpsNames) {
        state.tpsData = tpsNames.map((tpsItem, index) => ({
            name: typeof tpsItem === 'string' ? tpsItem : (tpsItem?.name || tpsItem?.Name || `TPS ${index + 1}`),
            index: index,
            lokasiAngkutId: typeof tpsItem === 'string' ? '' : (tpsItem?.lokasiAngkutId || tpsItem?.LokasiAngkutID || ''),
            spjDetailId: typeof tpsItem === 'string' ? '' : (tpsItem?.spjDetailId || tpsItem?.SpjDetailID || ''),
            latitude: '',
            longitude: '',
            alamatJalan: '',
            waktuKedatangan: '',
            fotoKedatangan: [],
            fotoKedatanganFileNames: [],
            fotoKedatanganUploaded: false,
            timbangan: [],
            totalOrganik: 0,
            totalAnorganik: 0,
            totalResidu: 0,
            totalTimbangan: 0,
            fotoPetugas: [],
            fotoPetugasFileNames: [],
            fotoPetugasUploaded: false,
            namaPetugas: '',
            submitted: false
          }));
        state.hasRequestedLocation = new Array(tpsNames.length).fill(false);
    }

  function renderTpsSelection() {
    elements.tpsCheckboxesContainer.innerHTML = "";
    state.availableTpsList.forEach((tpsName) => {
      const wrapper = document.createElement("label");
      wrapper.className =
        "flex items-center gap-3 p-3 rounded-xl border border-gray-200 hover:bg-gray-50 cursor-pointer transition";

      const checkbox = document.createElement("input");
      checkbox.type = "checkbox";
      checkbox.value = tpsName;
      checkbox.className =
        "w-5 h-5 rounded border-gray-300 text-upst focus:ring-upst";
      checkbox.checked = true;

      const label = document.createElement("span");
      label.className = "text-sm font-bold text-gray-700";
      label.textContent = tpsName;

      wrapper.appendChild(checkbox);
      wrapper.appendChild(label);
      elements.tpsCheckboxesContainer.appendChild(wrapper);
    });
  }

    async function handleConfirmTps() {
        const checkboxes = elements.tpsCheckboxesContainer.querySelectorAll('input[type="checkbox"]:checked');
        state.selectedTpsList = Array.from(checkboxes).map(cb => cb.value);
        
        if (state.selectedTpsList.length === 0) {
            showToast('Pilih minimal 1 TPS untuk diangkut!', 'error');
            return;
        }
        
        initializeTpsData(state.selectedTpsList);
        await loadRecordsForCurrentSpj();
        elements.tpsSelectionContainer.style.display = 'none';
        elements.tpsTabsContainer.style.display = 'block';
        
        if (state.selectedTpsList.length === 1) {
            renderSingleForm();
        } else {
            renderTabs();
            renderTpsForm();
        }
    }

  function renderSingleForm() {
    elements.tpsTabsEl.style.display = "none";
    state.activeTpsIndex = 0;
    renderTpsForm();
  }

  function renderTabs() {
    elements.tpsTabsEl.style.display = "block";
    elements.tpsTabsEl.className = "mb-4";

    const activeTps = state.tpsData[state.activeTpsIndex];
    const isActiveSubmitted = !!(activeTps && activeTps.submitted);
    const selectClass = isActiveSubmitted
      ? "w-full rounded-xl border border-green-300 bg-green-50 text-green-700 px-3 py-2 text-sm font-bold"
      : "w-full rounded-xl border border-gray-200 bg-white text-gray-700 px-3 py-2 text-sm font-bold";

    const optionsHtml = state.tpsData
      .map((tps, index) => {
        const marker = tps.submitted ? "✅ " : "";
        return `<option value="${index}" ${index === state.activeTpsIndex ? "selected" : ""}>${marker}${tps.name}</option>`;
      })
      .join("");

    elements.tpsTabsEl.innerHTML = `
            <label class="block text-xs font-semibold text-gray-500 mb-1">Pilih TPS</label>
            <select id="tps-dropdown" class="${selectClass}">
                ${optionsHtml}
            </select>
        `;

    const dropdown = elements.tpsTabsEl.querySelector("#tps-dropdown");
    if (dropdown) {
      dropdown.addEventListener("change", (e) => {
        const nextIndex = parseInt(e.target.value, 10);
        if (!Number.isNaN(nextIndex)) {
          switchToTps(nextIndex);
        }
      });
    }
  }

  function switchToTps(index) {
    state.activeTpsIndex = index;
    renderTabs();
    renderTpsForm();

    const switchedTps = state.tpsData[index];
    if (!state.hasRequestedLocation[index] && !switchedTps?.submitted) {
      state.hasRequestedLocation[index] = true;
      getLocationUpdate(index);
    }

    updateAllTotals();
  }

  function renderTpsForm() {
    const tps = state.tpsData[state.activeTpsIndex];
    const showTpsName =
      state.selectedTpsList.length > 1 || state.availableTpsList.length > 0;
    const submitState = getSubmitState(tps);
    const actionMarkup = tps.submitted
      ? `<div class="flex items-center justify-center gap-2 rounded-xl border border-green-200 bg-green-50 px-4 py-3 text-sm">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
            <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          <span class="font-medium text-green-700">
            Data <strong class="font-bold text-green-800">${showTpsName ? tps.name + ' ' : ''}</strong>sudah disubmit
          </span>
        </div>`
      : `
                <div class="flex gap-3">
                    <a href="/upst/detail-penjemputan/batal" class="w-1/3 text-center bg-red-500 text-white py-3 rounded-xl font-bold text-sm">Batal</a>
                    <button type="submit" ${submitState.canSubmit ? "" : "disabled"} class="w-2/3 py-3 rounded-xl font-bold text-sm transition ${submitState.canSubmit ? "bg-upst text-white hover:brightness-110" : "bg-gray-300 text-gray-500 cursor-not-allowed"}">Submit${showTpsName ? " " + tps.name : ""}</button>
                </div>
                ${submitState.canSubmit ? '' : `<p class="submit-state-message text-[11px] text-center text-red-500 font-medium">${submitState.message}</p>`}
            `;

    elements.tpsContentContainer.innerHTML = `
            <form class="space-y-5 pb-8" data-tps-index="${tps.index}">
                <input type="hidden" class="tps-lokasi-angkut-id" value="${tps.lokasiAngkutId || ""}" />
                <input type="hidden" class="tps-spj-detail-id" value="${tps.spjDetailId || ""}" />
                <input type="hidden" class="tps-latitude" value="${tps.latitude}" />
                <input type="hidden" class="tps-longitude" value="${tps.longitude}" />
                <input type="hidden" class="tps-alamat-jalan" value="${tps.alamatJalan}" />
                <input type="hidden" class="tps-total-timbangan" value="${tps.totalTimbangan}" />
                <input type="hidden" class="tps-total-organik" value="${tps.totalOrganik}" />
                <input type="hidden" class="tps-total-anorganik" value="${tps.totalAnorganik}" />
                <input type="hidden" class="tps-total-residu" value="${tps.totalResidu}" />

                ${renderSection1Kedatangan(tps, showTpsName)}
                ${renderSection2Timbangan(tps, showTpsName)}
                ${renderSection3Petugas(tps)}

                ${actionMarkup}
                <p id="auto-save-status" class="text-[11px] text-amber-500 text-center font-medium" style="opacity:0;transition:opacity 0.4s"></p>
            </form>
        `;

    attachTpsFormListeners();
    restoreTpsTimbanganItems();
    restorePhotoPreview();
  }

  function renderSection1Kedatangan(tps, showTpsName) {
    return `
            <section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
                <div class="flex items-center gap-3">
                    <div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">1</div>
                    <div>
                        <h3 class="font-black text-gray-800">Foto Kedatangan${showTpsName ? " - " + tps.name : ""}</h3>
                        <p class="text-xs text-gray-500">Upload foto kedatangan</p>
                    </div>
                </div>

                <label class="block text-xs font-semibold text-gray-600">Upload Foto Kedatangan</label>
                <input type="file" class="tps-foto-kedatangan block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" />
                <div class="tps-preview-kedatangan space-y-2"></div>
                
                <div class="kedatangan-upload-state">${getKedatanganUploadStateMarkup(tps)}</div>

                <div class="grid grid-cols-2 gap-2">
                    <div>
                        <label class="block text-xs font-semibold text-gray-600 mb-1">Latitude</label>
                        <input type="text" class="tps-display-latitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.latitude}" />
                    </div>
                    <div>
                        <label class="block text-xs font-semibold text-gray-600 mb-1">Longitude</label>
                        <input type="text" class="tps-display-longitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.longitude}" />
                    </div>
                </div>

                <div>
                    <label class="block text-xs font-semibold text-gray-600 mb-1">Waktu Kedatangan</label>
                    <input type="text" class="tps-waktu-kedatangan w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.waktuKedatangan}" />
                </div>
            </section>
        `;
  }

  function renderSection2Timbangan(tps, showTpsName) {
    return `
            <section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
                <div class="flex items-center gap-3">
                    <div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">2</div>
                    <div>
                        <h3 class="font-black text-gray-800">Foto Timbang Sampah</h3>
                        <p class="text-xs text-gray-500">Upload foto timbangan, berat auto terisi</p>
                    </div>
                </div>

                <div class="tps-timbangan-repeater space-y-3"></div>

                <button type="button" class="tps-btn-add-timbangan w-full border border-dashed border-upst text-upst rounded-xl py-2 text-xs font-bold transition">
                    + Tambah Foto Timbangan
                </button>
            </section>
        `;
  }

  function renderSection3Petugas(tps) {
    return `
            <section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
                <div class="flex items-center gap-3">
                    <div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">3</div>
                    <div>
                        <h3 class="font-black text-gray-800">Foto Petugas</h3>
                        <p class="text-xs text-gray-500">Upload dokumentasi petugas</p>
                    </div>
                </div>

                <label class="block text-xs font-semibold text-gray-600">Upload Foto Petugas</label>
                <input type="file" class="tps-foto-petugas block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" />
                <div class="tps-preview-petugas space-y-2"></div>
                
                <div class="petugas-upload-state">${getPetugasUploadStateMarkup(tps)}</div>
                
                <div>
                    <label class="block text-xs font-semibold text-gray-600 mb-1">Nama Petugas</label>
                    <input type="text" class="tps-nama-petugas w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" placeholder="Masukkan nama petugas" value="${tps.namaPetugas}" />
                </div>
            </section>
        `;
  }

  function attachTpsFormListeners() {
    const form = elements.tpsContentContainer.querySelector("form");
    const tps = state.tpsData[state.activeTpsIndex];
    if (tps.submitted) return;

    const fotoKedatanganInput = form.querySelector(".tps-foto-kedatangan");
    const fotoPetugasInput = form.querySelector(".tps-foto-petugas");
    const namaPetugasInput = form.querySelector(".tps-nama-petugas");
    const btnAddTimbangan = form.querySelector(".tps-btn-add-timbangan");

        fotoKedatanganInput.addEventListener('change', function() {
            replaceSelectedPhotos(
              tps,
              'fotoKedatangan',
              this.files,
              form.querySelector('.tps-preview-kedatangan')
            );
            updateWaktuKedatangan(tps.index);
            refreshKedatanganUploadState(form);
        });

        fotoPetugasInput.addEventListener('change', function() {
            replaceSelectedPhotos(
              tps,
              'fotoPetugas',
              this.files,
              form.querySelector('.tps-preview-petugas')
            );
            refreshPetugasUploadState(form);
        });

        namaPetugasInput.addEventListener('input', function() {
            tps.namaPetugas = this.value;
            refreshPetugasUploadState(form);
          scheduleAutoSave(tps.index);
        });

        namaPetugasInput.addEventListener('blur', function() {
            tps.namaPetugas = this.value;
          scheduleAutoSave(tps.index);
        });

        btnAddTimbangan.addEventListener('click', function() {
            createTimbanganItem(form.querySelector('.tps-timbangan-repeater'));
            syncTimbanganToTpsData();
            refreshSubmitButtonState(form);
        });

    const btnUploadKedatangan = form.querySelector(
      ".tps-btn-upload-kedatangan",
    );
    if (btnUploadKedatangan) {
      btnUploadKedatangan.addEventListener("click", uploadFotoKedatangan);
    }

    const btnUploadPetugas = form.querySelector(".tps-btn-upload-petugas");
    if (btnUploadPetugas) {
      btnUploadPetugas.addEventListener("click", uploadFotoPetugas);
    }

    form.addEventListener("submit", function (e) {
      e.preventDefault();
      submitTpsData();
    });
  }

  function restoreTpsTimbanganItems() {
    const tps = state.tpsData[state.activeTpsIndex];
    const form = elements.tpsContentContainer.querySelector("form");
    const repeater = form.querySelector(".tps-timbangan-repeater");

    if (tps.timbangan.length === 0) {
      createTimbanganItem(repeater);
    } else {
      tps.timbangan.forEach((timb) => {
        createTimbanganItem(repeater, timb);
      });
    }
  }

  function restorePhotoPreview() {
    const tps = state.tpsData[state.activeTpsIndex];
    const form = elements.tpsContentContainer.querySelector("form");
    if (!form) return;

    const previewKedatangan = form.querySelector('.tps-preview-kedatangan');
    if (previewKedatangan) {
      renderPhotoPreviewByState({
        files: tps.fotoKedatangan,
        uploaded: tps.fotoKedatanganUploaded,
        fileNames: tps.fotoKedatanganFileNames,
        container: previewKedatangan,
      });
    }

    const previewPetugas = form.querySelector('.tps-preview-petugas');
    if (previewPetugas) {
      renderPhotoPreviewByState({
        files: tps.fotoPetugas,
        uploaded: tps.fotoPetugasUploaded,
        fileNames: tps.fotoPetugasFileNames,
        container: previewPetugas,
      });
    }
  }

  function renderPhotoPreviewByState({ files, uploaded, fileNames, container }) {
    if (!container) return;

    container.innerHTML = '';

    if (Array.isArray(files) && files.length > 0) {
      renderStoredPhotos(files, container);
      return;
    }

    if (uploaded && Array.isArray(fileNames) && fileNames.length > 0) {
      renderServerImagePreview(fileNames, container);
    }
  }

  function replaceSelectedPhotos(tps, fieldName, files, previewContainer) {
    const fileNamesField = `${fieldName}FileNames`;
    const uploadedField = `${fieldName}Uploaded`;

    tps[fieldName] = Array.from(files || []);
    tps[fileNamesField] = [];
    tps[uploadedField] = false;

    renderPhotoPreviewByState({
      files: tps[fieldName],
      uploaded: tps[uploadedField],
      fileNames: tps[fileNamesField],
      container: previewContainer,
    });

    saveState();
  }

  function renderStoredPhotos(files, container) {
    container.innerHTML = "";
    container.className = "space-y-2";

    files.forEach((file, index) => {
      const item = document.createElement("div");
      item.className =
        "rounded-xl border border-gray-200 overflow-hidden bg-black";

          const imageUrl = getStoredPhotoUrl(file);
            item.innerHTML = `
                <div class="h-44 bg-black/80">
                    <img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
                </div>
                `;

      const img = item.querySelector(".preview-multi-image");
      if (img && isBrowserFile(resolveStoredPhoto(file))) {
        img.onload = function () {
          URL.revokeObjectURL(imageUrl);
        };
      }
      container.appendChild(item);
    });
  }

  function updateWaktuKedatangan(tpsIndex = state.activeTpsIndex) {
    const tps = state.tpsData[tpsIndex];
    if (!tps) return;
    const now = new Date();
    const formatted = now.toLocaleString("id-ID", {
      day: "2-digit",
      month: "2-digit",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
    });
    tps.waktuKedatangan = formatted;

    if (tpsIndex === state.activeTpsIndex) {
      const form = elements.tpsContentContainer.querySelector("form");
      const displayWaktu = form?.querySelector(".tps-waktu-kedatangan");
      if (displayWaktu) displayWaktu.value = formatted;
    }

    getLocationUpdate(tpsIndex);
    saveState();
  }

  function getLocationUpdate(tpsIndex = state.activeTpsIndex) {
    if (!("geolocation" in navigator)) return;

    navigator.geolocation.getCurrentPosition(
      function (position) {
        const lat = position.coords.latitude.toFixed(6);
        const lng = position.coords.longitude.toFixed(6);
        reverseGeocode(lat, lng, tpsIndex);
      },
      function () {
        console.log("Lokasi tidak diizinkan");
      },
    );
  }

  function reverseGeocode(lat, lng, tpsIndex = state.activeTpsIndex) {
    const tps = state.tpsData[tpsIndex];
    if (!tps) return;
    fetch(
      `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`,
    )
      .then((res) => res.json())
      .then((data) => {
        const address = data.display_name || `${lat}, ${lng}`;
        updateTpsLocation(lat, lng, address, tpsIndex);
      })
      .catch(() => {
        updateTpsLocation(lat, lng, `${lat}, ${lng}`, tpsIndex);
      });
  }

    function updateTpsLocation(lat, lng, address, tpsIndex = state.activeTpsIndex) {
        const tps = state.tpsData[tpsIndex];
        if (!tps) return;
        tps.latitude = lat;
        tps.longitude = lng;
        tps.alamatJalan = address;
        
        const form = tpsIndex === state.activeTpsIndex ? elements.tpsContentContainer.querySelector('form') : null;
        if (form) {
            const latInput = form.querySelector('.tps-display-latitude');
            const lngInput = form.querySelector('.tps-display-longitude');
            if (latInput) latInput.value = lat;
            if (lngInput) lngInput.value = lng;
        }

    }

  function updateMultiPreview(input, previewContainer) {
    if (!input || !previewContainer) return;

    previewContainer.innerHTML = "";
    previewContainer.className = "space-y-2";

    if (!input.files || input.files.length === 0) return;

    Array.from(input.files).forEach((file, index) => {
      const item = document.createElement("div");
      item.className =
        "rounded-xl border border-gray-200 overflow-hidden bg-black";

            const imageUrl = URL.createObjectURL(file);
            item.innerHTML = `
                <div class="h-44 bg-black/80">
                    <img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
                </div>
            `;

            const img = item.querySelector('.preview-multi-image');
            if (img) {
                img.onload = function() {
                    URL.revokeObjectURL(imageUrl);
                };
            }
            previewContainer.appendChild(item);
        });
    }

    function renderServerImagePreview(fileUrls, container) {
        container.innerHTML = '';
        if (!fileUrls || fileUrls.length === 0) return;
        container.className = 'space-y-2';

        fileUrls.forEach((url, index) => {
            const item = document.createElement('div');
            item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black';
        const imageUrl = getStoredPhotoUrl(url);
        const isUrl = Boolean(imageUrl) && (imageUrl.startsWith('/') || imageUrl.startsWith('http'));

            if (isUrl) {
                item.innerHTML = `
                    <div class="h-44 bg-black/80">
              <img src="${imageUrl}" alt="Foto ${index + 1}" class="w-full h-full object-contain" loading="lazy" />
                    </div>
                `;
            } else {
                item.className = 'rounded-xl border border-green-200 bg-green-50 h-44 flex items-center justify-center';
                item.innerHTML = `<p class="text-xs text-green-700 font-semibold px-3">&#10003; Foto ${index + 1}</p>`;
            }

            container.appendChild(item);
        });
    }

    function createTimbanganItem(repeater, existingData = null) {
        const photoNumber = repeater.children.length + 1;
        
        const item = document.createElement('div');
        item.className = 'timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50';
        item.dataset.photoNumber = photoNumber;
        
        const weight = existingData ? (existingData.weight || 0) : 0;
        const jenisSampah = existingData ? (existingData.jenisSampah || CONFIG.DEFAULT_JENIS) : CONFIG.DEFAULT_JENIS;
        const hasFileBlob = isBrowserFile(existingData?.file);
        const hasFile = Boolean(hasStoredPhoto(existingData?.file) || existingData?.fotoFileName);
        const isUploaded = Boolean(existingData?.uploaded);
        const ocrInfoText = existingData && existingData.ocrInfo ? existingData.ocrInfo : (hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.');
        
        item.innerHTML = `
            <div class="flex items-center justify-between">
                <p class="text-xs font-bold text-gray-600">Item Timbangan #${photoNumber}</p>
                <button type="button" class="btn-remove-timbangan text-[11px] font-bold text-red-500">Hapus</button>
            </div>
            <input type="file" name="FotoTimbangan" accept="image/*" class="input-foto-timbangan block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" />
            <div class="${(hasFileBlob || (hasFile && getStoredPhotoUrl(existingData?.file || existingData?.fotoFileName))) ? '' : 'hidden'} input-preview-wrap relative rounded-xl overflow-hidden border border-gray-200 bg-black">
                <img class="input-preview-image w-full h-44 object-contain" alt="Preview foto timbangan" />
            </div>
            <p class="text-[11px] text-gray-500 input-ocr-info">${ocrInfoText}</p>
            <div class="timbangan-upload-state">${getTimbanganUploadStateMarkup(hasFile, isUploaded, weight > 0)}</div>
            <div class="grid grid-cols-1 gap-2">
                <div>
                    <label class="block text-xs font-semibold text-gray-600 mb-1">Jenis Sampah</label>
                    <select class="input-jenis-sampah w-full rounded-xl border border-gray-200 px-3 py-2 text-sm">
                        ${CONFIG.JENIS_SAMPAH.map((js) => `<option value="${js}" ${js === jenisSampah ? "selected" : ""}>${js}</option>`).join("")}
                    </select>
                </div>
                <div>
                    <label class="block text-xs font-semibold text-gray-600 mb-1">Berat (kg)</label>
                    <input type="text" inputmode="decimal" class="input-berat-timbangan-display w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" placeholder="Contoh: 54,45" value="${weight > 0 ? formatWeightDisplay(weight) : ""}" />
                    <input type="hidden" class="input-berat-timbangan-value" value="${weight.toFixed(2)}" />
                </div>
            </div>
        `;

    const fileInput = item.querySelector(".input-foto-timbangan");
    const previewWrap = item.querySelector(".input-preview-wrap");
    const previewImage = item.querySelector(".input-preview-image");
    const ocrInfoEl = item.querySelector(".input-ocr-info");
    const weightInputDisplay = item.querySelector(
      ".input-berat-timbangan-display",
    );
    const weightInputValue = item.querySelector(".input-berat-timbangan-value");
    const jenisSampahSelect = item.querySelector(".input-jenis-sampah");
    const removeBtn = item.querySelector(".btn-remove-timbangan");

        if (existingData && hasStoredPhoto(existingData.file)) {
          const localUrl = getStoredPhotoUrl(existingData.file);
            previewImage.src = localUrl;
            previewWrap.classList.remove('hidden');
            previewImage.onload = function() {
            if (!isBrowserFile(resolveStoredPhoto(existingData.file))) return;
                URL.revokeObjectURL(localUrl);
            };
        } else if (existingData && existingData.fotoFileName && existingData.fotoFileName.startsWith('/')) {
            previewImage.src = existingData.fotoFileName;
            previewWrap.classList.remove('hidden');
        }

        fileInput.addEventListener('change', async function() {
            if (fileInput.files && fileInput.files[0]) {
                const originalFile = fileInput.files[0];
                
                const watermarkedFile = await applyWatermark(originalFile, photoNumber);
                
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(watermarkedFile);
                fileInput.files = dataTransfer.files;
                
                const localUrl = URL.createObjectURL(watermarkedFile);
                previewImage.src = localUrl;
                previewWrap.classList.remove('hidden');
                previewImage.onload = function() {
                    URL.revokeObjectURL(localUrl);
                };

                await autoFillWeight(watermarkedFile, weightInputDisplay, ocrInfoEl);
                const parsed = parseWeightInput(weightInputDisplay.value);
                weightInputValue.value = parsed.toFixed(2);
                updateTpsTotalTimbangan();
                syncTimbanganToTpsData();
                
                const tps = state.tpsData[state.activeTpsIndex];
                const itemIndex = Array.from(repeater.children).indexOf(item);
                if (itemIndex >= 0 && tps.timbangan[itemIndex]) {
                    tps.timbangan[itemIndex].uploaded = false;
                    tps.timbangan[itemIndex].fotoFileName = '';
                    refreshTimbanganUploadState(item);
                }
            }
        });

        weightInputDisplay.addEventListener('input', function() {
            const cleaned = this.value.replace(/[^0-9.,]/g, '');
            this.value = cleaned;
            const parsed = parseWeightInput(cleaned);
            weightInputValue.value = parsed.toFixed(2);
            updateTpsTotalTimbangan();
            syncTimbanganToTpsData();
            refreshTimbanganUploadState(item);
            const form = elements.tpsContentContainer.querySelector('form');
            if (form) refreshSubmitButtonState(form);
            scheduleAutoSave(state.activeTpsIndex);
        });

    weightInputDisplay.addEventListener("blur", function () {
      const parsed = parseWeightInput(this.value);
      if (parsed > 0) {
        this.value = formatWeightDisplay(parsed);
        weightInputValue.value = parsed.toFixed(2);
      } else {
        this.value = "";
        weightInputValue.value = "0.00";
      }
      updateTpsTotalTimbangan();
      syncTimbanganToTpsData();
      refreshTimbanganUploadState(item);
      const form = elements.tpsContentContainer.querySelector("form");
      if (form) refreshSubmitButtonState(form);
    });

        jenisSampahSelect.addEventListener('change', function() {
            updateTpsTotalTimbangan();
            syncTimbanganToTpsData();
            const form = elements.tpsContentContainer.querySelector('form');
            if (form) refreshSubmitButtonState(form);
        });

        removeBtn.addEventListener('click', function() {
            item.remove();
            const form = elements.tpsContentContainer.querySelector('form');
            const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null;
            
            if (repeater) {
                renumberTimbanganItems(repeater);
                if (repeater.children.length === 0) {
                    createTimbanganItem(repeater);
                }
            }
            
            updateTpsTotalTimbangan();
            syncTimbanganToTpsData();
            if (form) refreshSubmitButtonState(form);
        });

    repeater.appendChild(item);
    refreshTimbanganUploadState(item);
    return item;
  }

  function getTimbanganUploadStateMarkup(hasFile, isUploaded, hasValidWeight) {
    if (!hasFile) {
      return '<p class="text-[11px] text-gray-400">Pilih foto timbangan terlebih dahulu</p>';
    }

    if (isUploaded) {
      return `
                <div class="text-center text-xs text-green-600 font-bold py-2 upload-success-message">✓ Foto timbangan sudah diupload</div>
                <p class="text-[11px] text-gray-500 text-center">Jika ingin revisi, pilih file baru diatas. Status upload akan tereset otomatis.</p>
            `;
    }

    if (!hasValidWeight) {
      return `
                <button type="button" disabled class="btn-upload-timbangan w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload Foto Timbangan Ini</button>
                <p class="text-[11px] text-red-500 text-center">Isi berat manual dulu sebelum upload jika berat tidak terbaca.</p>
            `;
    }

    return `
            <button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload Foto Timbangan Ini</button>
            <p class="text-[11px] text-amber-600 text-center">Foto siap diupload.</p>
        `;
  }

  function getKedatanganUploadStateMarkup(tps) {
    if (tps.fotoKedatanganUploaded) {
      return '<div class="text-center text-xs text-green-600 font-bold py-2">✓ Foto kedatangan sudah diupload</div>';
    }
    if (!tps.fotoKedatangan.length) {
      return "";
    }
    return `<button type="button" class="tps-btn-upload-kedatangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload ${tps.fotoKedatangan.length} Foto Kedatangan</button>`;
  }

  function refreshKedatanganUploadState(form) {
    const stateContainer = form.querySelector(".kedatangan-upload-state");
    if (!stateContainer) return;

    const tps = state.tpsData[state.activeTpsIndex];
    stateContainer.innerHTML = getKedatanganUploadStateMarkup(tps);

    const btnUploadKedatangan = stateContainer.querySelector(
      ".tps-btn-upload-kedatangan",
    );
    if (btnUploadKedatangan) {
      btnUploadKedatangan.addEventListener("click", uploadFotoKedatangan);
    }

    refreshSubmitButtonState(form);
  }

  function getPetugasUploadStateMarkup(tps) {
    if (tps.fotoPetugasUploaded) {
      return '<div class="text-center text-xs text-green-600 font-bold py-2">✓ Foto petugas sudah diupload</div>';
    }

    if (!tps.fotoPetugas.length) {
      return "";
    }

    if (!tps.namaPetugas.trim()) {
      return `
                <button type="button" disabled class="tps-btn-upload-petugas w-full bg-gray-300 text-gray-500 py-2 rounded-xl font-bold text-xs cursor-not-allowed">Upload ${tps.fotoPetugas.length} Foto Petugas</button>
                <p class="text-[11px] text-red-500 text-center">Isi nama petugas terlebih dahulu</p>
            `;
    }

    return `<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">Upload ${tps.fotoPetugas.length} Foto Petugas</button>`;
  }

  function refreshPetugasUploadState(form) {
    const stateContainer = form.querySelector(".petugas-upload-state");
    if (!stateContainer) return;

    const tps = state.tpsData[state.activeTpsIndex];
    stateContainer.innerHTML = getPetugasUploadStateMarkup(tps);

    const btnUploadPetugas = stateContainer.querySelector(
      ".tps-btn-upload-petugas:not([disabled])",
    );
    if (btnUploadPetugas) {
      btnUploadPetugas.addEventListener("click", uploadFotoPetugas);
    }

    refreshSubmitButtonState(form);
  }

    function isTimbanganItemReady(timbanganItem) {
        return Boolean(timbanganItem?.file || timbanganItem?.fotoFileName) && Boolean(timbanganItem?.uploaded) && (timbanganItem?.weight || 0) > 0;
    }

    function getSubmitState(tps) {
      if (tps?.submitted) {
        return { canSubmit: false, message: '' };
      }

        if (!tps.fotoKedatanganUploaded) {
            if (!tps.fotoKedatangan.length) {
                return { canSubmit: false, message: 'Silakan pilih dan upload foto kedatangan terlebih dahulu.' };
            }
            return { canSubmit: false, message: 'Silakan upload foto kedatangan terlebih dahulu.' };
        }

    if (!tps.timbangan.length) {
      return {
        canSubmit: false,
        message: "Tambahkan minimal 1 data timbangan sebelum submit.",
      };
    }

    if (tps.timbangan.some((item) => !isTimbanganItemReady(item))) {
      return {
        canSubmit: false,
        message:
          "Pastikan semua foto timbangan sudah diupload dan beratnya valid.",
      };
    }

        if (!tps.fotoPetugasUploaded) {
            if (!tps.fotoPetugas.length) {
                return { canSubmit: false, message: 'Silakan pilih dan upload foto petugas terlebih dahulu.' };
            }
            return { canSubmit: false, message: 'Silakan upload foto petugas terlebih dahulu' };
        }

    if (!tps.namaPetugas.trim()) {
      return {
        canSubmit: false,
        message: "Isi nama petugas dulu sebelum submit.",
      };
    }

    return { canSubmit: true, message: "" };
  }

  function refreshSubmitButtonState(form) {
    const submitButton = form.querySelector('button[type="submit"]');
    const tps = state.tpsData[state.activeTpsIndex];
    if (tps?.submitted || !submitButton) return;

    let messageEl = form.querySelector(".submit-state-message");
    const submitState = getSubmitState(tps);

    submitButton.disabled = !submitState.canSubmit;
    submitButton.className = `w-2/3 py-3 rounded-xl font-bold text-sm transition ${submitState.canSubmit ? "bg-upst text-white hover:brightness-110" : "bg-gray-300 text-gray-500 cursor-not-allowed"}`;

    if (!messageEl) {
      messageEl = document.createElement("p");
      messageEl.className =
        "submit-state-message text-[11px] text-center text-red-500 font-medium";
      submitButton
        .closest(".flex.gap-3")
        ?.insertAdjacentElement("afterend", messageEl);
    }

    if (submitState.canSubmit) {
      messageEl.textContent = "";
      messageEl.classList.add("hidden");
    } else {
      messageEl.textContent = submitState.message;
      messageEl.classList.remove("hidden");
    }
  }

  function refreshTimbanganUploadState(item) {
    const stateContainer = item.querySelector(".timbangan-upload-state");
    if (!stateContainer) return;

        const repeater = item.parentElement;
        const itemIndex = repeater ? Array.from(repeater.children).indexOf(item) : -1;
        const tps = state.tpsData[state.activeTpsIndex];
        const currentData = itemIndex >= 0 ? tps.timbangan[itemIndex] : null;
        const fileInput = item.querySelector('.input-foto-timbangan');
        const hasFile = Boolean(currentData?.file || fileInput?.files?.[0] || currentData?.fotoFileName);
        const isUploaded = Boolean(currentData?.uploaded);
        const weightInputValue = item.querySelector('.input-berat-timbangan-value');
        const currentWeight = currentData?.weight ?? parseWeightInput(weightInputValue?.value || '0');
        const hasValidWeight = currentWeight > 0;

    stateContainer.innerHTML = getTimbanganUploadStateMarkup(
      hasFile,
      isUploaded,
      hasValidWeight,
    );

    const uploadBtn = stateContainer.querySelector(".btn-upload-timbangan");
    if (uploadBtn) {
      uploadBtn.addEventListener("click", function () {
        const latestIndex = repeater
          ? Array.from(repeater.children).indexOf(item)
          : -1;
        uploadSingleFotoTimbangan(latestIndex, item);
      });
    }

    const form = elements.tpsContentContainer.querySelector("form");
    if (form) {
      refreshSubmitButtonState(form);
    }
  }

  function renumberTimbanganItems(repeater) {
    const items = repeater.querySelectorAll(".timbangan-item");
    items.forEach((item, index) => {
      const newNumber = index + 1;
      item.dataset.photoNumber = newNumber;

      const label = item.querySelector(".text-xs.font-bold.text-gray-600");
      if (label) {
        label.textContent = `Item Timbangan #${newNumber}`;
      }
    });
  }

  async function applyWatermark(file, photoNumber) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = function (e) {
        const img = new Image();
        img.onload = function () {
          const canvas = document.createElement("canvas");
          canvas.width = img.width;
          canvas.height = img.height;
          const ctx = canvas.getContext("2d");

          ctx.drawImage(img, 0, 0);

          const now = new Date();
          const days = [
            "Minggu",
            "Senin",
            "Selasa",
            "Rabu",
            "Kamis",
            "Jumat",
            "Sabtu",
          ];
          const months = [
            "Jan",
            "Feb",
            "Mar",
            "Apr",
            "Mei",
            "Jun",
            "Jul",
            "Agu",
            "Sep",
            "Okt",
            "Nov",
            "Des",
          ];

          const dayName = days[now.getDay()];
          const date = now.getDate().toString().padStart(2, "0");
          const month = months[now.getMonth()];
          const year = now.getFullYear();
          const hours = now.getHours().toString().padStart(2, "0");
          const minutes = now.getMinutes().toString().padStart(2, "0");
          const seconds = now.getSeconds().toString().padStart(2, "0");

          const timestamp = `${dayName}, ${date} ${month} ${year} • ${hours}:${minutes}:${seconds}`;

          const baseFontSize = Math.max(
            16,
            Math.min(img.width, img.height) * 0.025,
          );
          const fontFamily = "'Montserrat', 'Segoe UI', 'Roboto', sans-serif";

          const lines = [
            {
              text: `FOTO TIMBANG #${photoNumber}`,
              size: baseFontSize * 1.1,
              weight: "900",
              color: "#FFD700",
            },
            {
              text: `${state.nomorSpj}`,
              size: baseFontSize * 0.9,
              weight: "700",
              color: "#FFFFFF",
            },
            {
              text: timestamp,
              size: baseFontSize * 0.75,
              weight: "500",
              color: "#E2E8F0",
            },
          ];

          const paddingX = baseFontSize * 1.2;
          const paddingY = baseFontSize * 1.0;
          const lineGap = baseFontSize * 0.4;

          let maxWidth = 0;
          let totalHeight = 0;

          lines.forEach((line) => {
            ctx.font = `${line.weight} ${line.size}px ${fontFamily}`;
            const metrics = ctx.measureText(line.text);
            maxWidth = Math.max(maxWidth, metrics.width);
            totalHeight += line.size + lineGap;
          });
          totalHeight -= lineGap;

          const margin = baseFontSize * 1.5;
          const boxWidth = maxWidth + paddingX * 2;
          const boxHeight = totalHeight + paddingY * 2;
          const boxX = img.width - boxWidth - margin;
          const boxY = img.height - boxHeight - margin;

          ctx.save();

          ctx.beginPath();
          if (ctx.roundRect) {
            ctx.roundRect(boxX, boxY, boxWidth, boxHeight, baseFontSize * 0.8);
          } else {
            ctx.rect(boxX, boxY, boxWidth, boxHeight);
          }
          ctx.fillStyle = "rgba(15, 23, 42, 0.85)";
          ctx.fill();

          ctx.beginPath();
          const accentWidth = baseFontSize * 0.3;
          if (ctx.roundRect) {
            ctx.roundRect(
              boxX + boxWidth - accentWidth,
              boxY,
              accentWidth,
              boxHeight,
              [0, baseFontSize * 0.8, baseFontSize * 0.8, 0],
            );
          } else {
            ctx.rect(
              boxX + boxWidth - accentWidth,
              boxY,
              accentWidth,
              boxHeight,
            );
          }
          ctx.fillStyle = "#FFD700";
          ctx.fill();

          ctx.restore();

          ctx.shadowColor = "rgba(0, 0, 0, 0.6)";
          ctx.shadowBlur = 4;
          ctx.shadowOffsetX = 1;
          ctx.shadowOffsetY = 1;
          ctx.textAlign = "right";
          ctx.textBaseline = "top";

          let currentY = boxY + paddingY;
          const textRightLimit = boxX + boxWidth - paddingX - accentWidth;

          lines.forEach((line) => {
            ctx.font = `${line.weight} ${line.size}px ${fontFamily}`;
            ctx.fillStyle = line.color;
            ctx.fillText(line.text, textRightLimit, currentY);
            currentY += line.size + lineGap;
          });

          ctx.shadowColor = "transparent";

          canvas.toBlob(
            function (blob) {
              const watermarkedFile = new File([blob], file.name, {
                type: "image/jpeg",
                lastModified: Date.now(),
              });
              resolve(watermarkedFile);
            },
            "image/jpeg",
            0.95,
          );
        };
        img.onerror = reject;
        img.src = e.target.result;
      };
      reader.onerror = reject;
      reader.readAsDataURL(file);
    });
  }
  async function autoFillWeight(file, weightInput, ocrInfoEl) {
    let guessedWeight = 0;
    weightInput.placeholder = "Membaca angka dari foto...";
    if (ocrInfoEl) ocrInfoEl.textContent = "AI: memproses gambar...";

    try {
      const img = await readFileAsImage(file);
      let bestRawText = "";
      let isSuccess = false;

      for (const area of CONFIG.OCR_AREAS) {
        const cropCanvas = createCropCanvas(img, area);
        const cropFile = await canvasToJpegFile(
          cropCanvas,
          `crop-${area.id}.jpg`,
        );
        const aiResult = await requestOpenRouterWeight(cropFile);

        if (aiResult && aiResult.success && aiResult.weight) {
          guessedWeight = parseWeightInput(aiResult.weight);
          bestRawText = aiResult.raw || aiResult.weight;
          isSuccess = guessedWeight > 0;
          if (isSuccess) break;
        }

        if (aiResult && aiResult.raw) {
          bestRawText = aiResult.raw;
        }
      }

      if (ocrInfoEl) {
        const cleaned = (bestRawText || "").replace(/\s+/g, " ").trim();
        ocrInfoEl.textContent = isSuccess
          ? `AI terbaca: ${cleaned}`
          : cleaned
            ? `AI tidak valid: ${cleaned}`
            : "AI tidak menemukan angka valid.";
      }
    } catch (_) {
      guessedWeight = 0;
      if (ocrInfoEl) ocrInfoEl.textContent = "AI gagal diproses.";
    }

    if (guessedWeight > 0) {
      weightInput.value = formatWeightDisplay(guessedWeight);
      weightInput.placeholder = "Berat terdeteksi otomatis";
    } else {
      weightInput.placeholder = "Tidak terbaca otomatis, isi manual";
    }

    updateTpsTotalTimbangan();
  }

  function readFileAsImage(file) {
    return new Promise(function (resolve, reject) {
      const objectUrl = URL.createObjectURL(file);
      const img = new Image();
      img.onload = function () {
        URL.revokeObjectURL(objectUrl);
        resolve(img);
      };
      img.onerror = function () {
        URL.revokeObjectURL(objectUrl);
        reject(new Error("Gagal membaca gambar."));
      };
      img.src = objectUrl;
    });
  }

  function createCropCanvas(img, area) {
    const sx = Math.max(0, Math.floor(img.width * area.x));
    const sy = Math.max(0, Math.floor(img.height * area.y));
    const sw = Math.max(1, Math.floor(img.width * area.w));
    const sh = Math.max(1, Math.floor(img.height * area.h));

    const canvas = document.createElement("canvas");
    const scale = 2.2;
    canvas.width = Math.max(1, Math.floor(sw * scale));
    canvas.height = Math.max(1, Math.floor(sh * scale));
    const ctx = canvas.getContext("2d");
    if (!ctx) return canvas;

    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height);
    return canvas;
  }

  function canvasToJpegFile(canvas, fileName) {
    return new Promise(function (resolve, reject) {
      canvas.toBlob(
        function (blob) {
          if (!blob) {
            reject(new Error("Gagal membuat crop image."));
            return;
          }
          resolve(new File([blob], fileName, { type: "image/jpeg" }));
        },
        "image/jpeg",
        0.92,
      );
    });
  }

  async function requestOpenRouterWeight(imageFile) {
    const formData = new FormData();
    formData.append("Foto", imageFile);
    const response = await fetch("/upst/detail-penjemputan/ocr-timbangan", {
      method: "POST",
      body: formData,
    });
    const result = await response.json();
    if (!response.ok) {
      throw new Error(result.message || "Request OCR gagal.");
    }
    return result;
  }

  function updateTpsTotalTimbangan() {
    const tps = state.tpsData[state.activeTpsIndex];
    const form = elements.tpsContentContainer.querySelector("form");
    if (!form) return;

    let totalOrganik = 0.0;
    let totalAnorganik = 0.0;
    let totalResidu = 0.0;

    const repeater = form.querySelector(".tps-timbangan-repeater");
    const items = repeater.querySelectorAll(".timbangan-item");

    items.forEach(function (item) {
      const weightInput = item.querySelector(".input-berat-timbangan-value");
      const jenisSampahSelect = item.querySelector(".input-jenis-sampah");

      if (weightInput && jenisSampahSelect) {
        const value = parseWeightInput(weightInput.value || "0");
        const jenis = jenisSampahSelect.value;

        if (jenis === "Organik") {
          totalOrganik += value;
        } else if (jenis === "Anorganik") {
          totalAnorganik += value;
        } else if (jenis === "Residu") {
          totalResidu += value;
        }
      }
    });

    tps.totalOrganik = totalOrganik;
    tps.totalAnorganik = totalAnorganik;
    tps.totalResidu = totalResidu;
    tps.totalTimbangan = totalOrganik + totalAnorganik + totalResidu;

    const displayTotalOrganik = form.querySelector(
      ".tps-display-total-organik",
    );
    const displayTotalAnorganik = form.querySelector(
      ".tps-display-total-anorganik",
    );
    const displayTotalResidu = form.querySelector(".tps-display-total-residu");
    const displayTotal = form.querySelector(".tps-display-total");

    if (displayTotalOrganik)
      displayTotalOrganik.textContent = formatWeightDisplay(totalOrganik);
    if (displayTotalAnorganik)
      displayTotalAnorganik.textContent = formatWeightDisplay(totalAnorganik);
    if (displayTotalResidu)
      displayTotalResidu.textContent = formatWeightDisplay(totalResidu);
    if (displayTotal)
      displayTotal.textContent = formatWeightDisplay(tps.totalTimbangan);

    updateAllTotals();
    saveState();
  }

  function updateAllTotals() {
    let grandTotal = 0;
    let grandOrganik = 0;
    let grandAnorganik = 0;
    let grandResidu = 0;

    state.tpsData.forEach((tps) => {
      grandTotal += tps.totalTimbangan;
      grandOrganik += tps.totalOrganik;
      grandAnorganik += tps.totalAnorganik;
      grandResidu += tps.totalResidu;
    });

    if (elements.grandTotalDisplay) {
      elements.grandTotalDisplay.textContent = formatWeightDisplay(grandTotal);
    }
    if (elements.totalOrganikDisplay) {
      elements.totalOrganikDisplay.textContent =
        formatWeightDisplay(grandOrganik);
    }
    if (elements.totalAnorganikDisplay) {
      elements.totalAnorganikDisplay.textContent =
        formatWeightDisplay(grandAnorganik);
    }
    if (elements.totalResiduDisplay) {
      elements.totalResiduDisplay.textContent =
        formatWeightDisplay(grandResidu);
    }
    saveState();
  }

  function syncTimbanganToTpsData() {
    const tps = state.tpsData[state.activeTpsIndex];
    const form = elements.tpsContentContainer.querySelector("form");
    if (!form) return;

        const repeater = form.querySelector('.tps-timbangan-repeater');
        const items = repeater.querySelectorAll('.timbangan-item');
        const previousTimbangan = [...tps.timbangan];
        
        tps.timbangan = [];
        items.forEach((item, index) => {
            const fileInput = item.querySelector('.input-foto-timbangan');
            const weightValue = item.querySelector('.input-berat-timbangan-value');
            const jenisSampahSelect = item.querySelector('.input-jenis-sampah');
            const ocrInfo = item.querySelector('.input-ocr-info')?.textContent || 'OCR: belum diproses.';
            const existingData = previousTimbangan[index];
            
            tps.timbangan.push({
                file: fileInput.files[0] || (existingData ? existingData.file : null),
                fotoFileName: existingData ? existingData.fotoFileName || '' : '',
                weight: parseWeightInput(weightValue.value),
                jenisSampah: jenisSampahSelect.value,
                uploaded: existingData ? existingData.uploaded : false,
                ocrInfo
            });
        });
    }

    async function uploadSingleFotoTimbangan(itemIndex, targetItem = null) {
        const tps = state.tpsData[state.activeTpsIndex];
        
        if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) {
            showToast('Belum ada foto timbangan yang dipilih!', 'error');
            return;
        }
        
        const timbanganItem = tps.timbangan[itemIndex];
        if (timbanganItem.weight <= 0) {
            showToast('Berat belum valid. Isi manual dulu sebelum upload foto timbangan.', 'error');
            return;
        }

        if (!targetItem) {
            const form = elements.tpsContentContainer.querySelector('form');
            const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null;
            const items = repeater ? repeater.querySelectorAll('.timbangan-item') : [];
            targetItem = items[itemIndex] || null;
        }

        const uploadBtn = targetItem ? targetItem.querySelector('.btn-upload-timbangan') : null;
        if (uploadBtn) {
            uploadBtn.disabled = true;
            uploadBtn.textContent = 'Mengupload...';
        }

        const formData = new FormData();
        formData.append('FotoTimbangan', timbanganItem.file);
        formData.append('NomorSpj', state.nomorSpj || '');
        formData.append('NamaTps', tps.name || 'TPS');
        formData.append('SpjDetailId', tps.spjDetailId || '');
        formData.append('LokasiAngkutId', tps.lokasiAngkutId || '');
        formData.append('ItemIndex', itemIndex);
        formData.append('JenisSampah', timbanganItem.jenisSampah || CONFIG.DEFAULT_JENIS);
        formData.append('Berat', Number(timbanganItem.weight || 0).toFixed(2));

        try {
            const res = await fetch(ENDPOINTS.uploadTimbangan, { method: 'POST', body: formData });
            const data = await res.json();
            if (res.ok && data.success) {
                timbanganItem.uploaded = true;
                timbanganItem.fotoFileName = data.fileUrl || data.fileName || '';
                const fotoInputTimb = targetItem?.querySelector('.input-foto-timbangan');
                if (fotoInputTimb) fotoInputTimb.value = '';
                showToast(data.message || `Foto timbangan #${itemIndex + 1} berhasil diupload.`, 'success');
            } else {
                showToast(data.message || 'Gagal upload foto timbangan.', 'error');
                if (uploadBtn) {
                    uploadBtn.disabled = false;
                    uploadBtn.textContent = 'Upload Foto Timbangan Ini';
                }
            }
        } catch (_) {
            showToast('Koneksi gagal saat upload foto timbangan.', 'error');
            if (uploadBtn) {
                uploadBtn.disabled = false;
                uploadBtn.textContent = 'Upload Foto Timbangan Ini';
            }
        }

    if (targetItem) {
      refreshTimbanganUploadState(targetItem);
    }

        syncTimbanganToTpsData();
        const form = elements.tpsContentContainer.querySelector('form');
        if (form) refreshSubmitButtonState(form);
        scheduleAutoSave(tps.index);
    }

    async function uploadFotoKedatangan() {
        const tps = state.tpsData[state.activeTpsIndex];
        if (tps.fotoKedatangan.length === 0) {
            showToast('Belum ada foto kedatangan yang dipilih!', 'error');
            return;
        }

        const form = elements.tpsContentContainer.querySelector('form');
        const btn = form ? form.querySelector('.tps-btn-upload-kedatangan') : null;
        if (btn) {
            btn.disabled = true;
            btn.textContent = 'Mengupload...';
        }

        const formData = new FormData();
        tps.fotoKedatangan.forEach(file => formData.append('FotoKedatangan', file));
        formData.append('NomorSpj', state.nomorSpj || '');
        formData.append('NamaTps', tps.name || 'TPS');
        formData.append('SpjDetailId', tps.spjDetailId || '');
        formData.append('LokasiAngkutId', tps.lokasiAngkutId || '');
        formData.append('WaktuKedatangan', tps.waktuKedatangan || '');
        formData.append('Latitude', tps.latitude || '');
        formData.append('Longitude', tps.longitude || '');
        formData.append('AlamatJalan', tps.alamatJalan || '');

        try {
            const res = await fetch(ENDPOINTS.uploadKedatangan, { method: 'POST', body: formData });
            const data = await res.json();
            if (res.ok && data.success) {
                tps.fotoKedatanganUploaded = true;
                tps.fotoKedatanganFileNames = data.fileUrls || data.fileNames || [];
                showToast(data.message || 'Foto kedatangan berhasil diupload.', 'success');
                if (form) {
                    const fotoInput = form.querySelector('.tps-foto-kedatangan');
                    if (fotoInput) fotoInput.value = '';
                    refreshKedatanganUploadState(form);
                }
                scheduleAutoSave(tps.index);
            } else {
                showToast(data.message || 'Gagal upload foto kedatangan.', 'error');
                if (btn) {
                    btn.disabled = false;
                    btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`;
                }
            }
        } catch (_) {
            showToast('Koneksi gagal saat upload foto kedatangan.', 'error');
            if (btn) {
                btn.disabled = false;
                btn.textContent = `Upload ${tps.fotoKedatangan.length} Foto Kedatangan`;
            }
        }
    }

    async function uploadFotoPetugas() {
        const tps = state.tpsData[state.activeTpsIndex];
        if (tps.fotoPetugas.length === 0) {
            showToast('Belum ada foto petugas yang dipilih!', 'error');
            return;
        }
        if (!tps.namaPetugas.trim()) {
            showToast('Nama petugas wajib diisi sebelum upload foto petugas!', 'error');
            return;
        }

        const form = elements.tpsContentContainer.querySelector('form');
        const btn = form ? form.querySelector('.tps-btn-upload-petugas:not([disabled])') : null;
        if (btn) {
            btn.disabled = true;
            btn.textContent = 'Mengupload...';
        }

        const formData = new FormData();
        tps.fotoPetugas.forEach(file => formData.append('FotoPetugas', file));
        formData.append('NomorSpj', state.nomorSpj || '');
        formData.append('NamaTps', tps.name || 'TPS');
        formData.append('SpjDetailId', tps.spjDetailId || '');
        formData.append('LokasiAngkutId', tps.lokasiAngkutId || '');
        formData.append('NamaPetugas', tps.namaPetugas || '');

        try {
            const res = await fetch(ENDPOINTS.uploadPetugas, { method: 'POST', body: formData });
            const data = await res.json();
            if (res.ok && data.success) {
                tps.fotoPetugasUploaded = true;
                tps.fotoPetugasFileNames = data.fileUrls || data.fileNames || [];
                showToast(data.message || 'Foto petugas berhasil diupload.', 'success');
                if (form) {
                    const fotoInput = form.querySelector('.tps-foto-petugas');
                    if (fotoInput) fotoInput.value = '';
                    refreshPetugasUploadState(form);
                }
                scheduleAutoSave(tps.index);
            } else {
                showToast(data.message || 'Gagal upload foto petugas.', 'error');
                if (btn) {
                    btn.disabled = false;
                    btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`;
                }
            }
        } catch (_) {
            showToast('Koneksi gagal saat upload foto petugas.', 'error');
            if (btn) {
                btn.disabled = false;
                btn.textContent = `Upload ${tps.fotoPetugas.length} Foto Petugas`;
            }
        }
    }

  function buildSubmitFormData(tps) {
    const formData = new FormData();
    formData.append("NomorSpj", state.nomorSpj || "");
    formData.append("LokasiAngkutId", tps.lokasiAngkutId || "");
    formData.append("SpjDetailId", tps.spjDetailId || "");
    formData.append("TpsName", tps.name);
    formData.append("Latitude", tps.latitude);
    formData.append("Longitude", tps.longitude);
    formData.append("AlamatJalan", tps.alamatJalan);
    formData.append("WaktuKedatangan", tps.waktuKedatangan);
    formData.append("TotalTimbangan", tps.totalTimbangan);
    formData.append("TotalOrganik", tps.totalOrganik);
    formData.append("TotalAnorganik", tps.totalAnorganik);
    formData.append("TotalResidu", tps.totalResidu);
    formData.append("NamaPetugas", tps.namaPetugas);

    tps.fotoKedatangan.forEach((file) => {
      if (isBrowserFile(file)) {
        formData.append("FotoKedatangan", file);
      }
    });

    tps.timbangan.forEach((timb) => {
      if (isBrowserFile(timb.file)) formData.append("FotoTimbangan", timb.file);
      formData.append("BeratTimbangan", timb.weight);
      formData.append("JenisSampahList", timb.jenisSampah);
    });

    tps.fotoPetugas.forEach((file) => {
      if (isBrowserFile(file)) {
        formData.append("FotoPetugas", file);
      }
    });

    const antiForgeryTokenEl = document.querySelector(
      '#upst-antiforgery input[name="__RequestVerificationToken"]',
    );
    if (antiForgeryTokenEl && antiForgeryTokenEl.value) {
      formData.append("__RequestVerificationToken", antiForgeryTokenEl.value);
    }

    return formData;
  }

  function markTpsSubmitted(tps) {
    tps.submitted = true;
    if (state.selectedTpsList.length > 1) {
      renderTabs();
    }
    saveState();
  }

  function clearStateIfAllSubmitted() {
    if (
      state.tpsData.length > 0 &&
      state.tpsData.every((item) => item.submitted)
    ) {
      clearState();
    }
  }

    async function submitTpsData() {
        const tps = state.tpsData[state.activeTpsIndex];
        const submitState = getSubmitState(tps);
        if (!submitState.canSubmit) {
            showToast(submitState.message, 'error');
            return;
        }

        const form = elements.tpsContentContainer.querySelector('form');
        const submitBtn = form ? form.querySelector('button[type="submit"]') : null;
        if (submitBtn) {
            submitBtn.disabled = true;
            submitBtn.textContent = `Menyimpan${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}...`;
        }

        const formData = buildSubmitFormData(tps);

        try {
            const response = await fetch(ENDPOINTS.submit, {
                method: 'POST',
            headers: {
              'X-Requested-With': 'XMLHttpRequest',
              'Accept': 'application/json'
            },
                body: formData
            });

          const result = await response.json().catch(() => null);

          if (response.ok && result?.success) {
                markTpsSubmitted(tps);
            showToast(result.message || `Data ${tps.name} berhasil disimpan!`, 'success');

                const allSubmitted = state.tpsData.every(item => item.submitted);
                if (allSubmitted) {
                    setTimeout(() => {
                        window.location.href = '/upst/detail-penjemputan/detail-selesai';
                    }, 1200);
                } else {
                    renderTabs();
                    renderTpsForm();
                }
            } else {
                showToast(result?.message || 'Gagal menyimpan data. Silakan coba lagi.', 'error');
                if (submitBtn) {
                    submitBtn.disabled = false;
                    submitBtn.textContent = `Submit${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}`;
                }
            }
        } catch (error) {
            console.error('Error:', error);
            showToast('Terjadi kesalahan saat menyimpan data.', 'error');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = `Submit${state.selectedTpsList.length > 1 ? ' ' + tps.name : ''}`;
            }
        }
    }

  function formatWeightDisplay(value) {
    if (isNaN(value)) return "0,00";
    return value.toFixed(2).replace(".", ",");
  }

  function parseWeightInput(value) {
    if (!value) return 0;
    const cleaned = value
      .toString()
      .trim()
      .replace(/\s/g, "")
      .replace(",", ".");
    const parsed = parseFloat(cleaned);
    return isNaN(parsed) ? 0 : parsed;
  }

    function formatFileSize(bytes) {
        const mb = bytes / (1024 * 1024);
        return `${mb.toFixed(2)} MB`;
    }

    function showToast(message, type = 'info') {
        let container = document.getElementById('espj-toast-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'espj-toast-container';
            container.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;';
            document.body.appendChild(container);
        }

        const toast = document.createElement('div');
        const bg = type === 'success' ? '#16a34a' : type === 'error' ? '#dc2626' : '#2563eb';
        toast.style.cssText = `background:${bg};color:#fff;padding:10px 18px;border-radius:16px;font-size:13px;font-weight:600;box-shadow:0 4px 24px rgba(0,0,0,.18);opacity:0;transition:opacity .25s;max-width:320px;text-align:center;`;
        toast.textContent = message;
        container.appendChild(toast);

        requestAnimationFrame(() => {
            toast.style.opacity = '1';
        });

        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        }, 3200);
    }

  return {
    init: init,
    hydrateFromApi: applyApiRecordData,
    setNomorSpj: function (nomorSpj) {
      state.nomorSpj = nomorSpj;
      saveState();
    },
  };
})();

document.addEventListener('DOMContentLoaded', async function() {
  const nomorSpjEl = document.querySelector('.text-gray-600.font-mono');
  const nomorSpj = nomorSpjEl ? nomorSpjEl.textContent.trim() : 'SPJ/07-2025/PKM/000476';

    try {
        const response = await fetch(`/driver/json/tps-list.json?_ts=${Date.now()}`, {
          cache: 'no-store',
          headers: {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            Pragma: 'no-cache'
          }
        });
        const data = await response.json();
        const tpsList = data.tpsList.map(tps => ({
            name: tps.name,
            lokasiAngkutId: tps.lokasiAngkutId,
            spjDetailId: tps.spjDetailId,
            id: tps.id
        }));
    await DetailPenjemputan.init(tpsList, nomorSpj);
    } catch (error) {
        console.error('Error loading TPS list:', error);
    }

  const platNomorEl = document.getElementById('plat-nomor');
  if (platNomorEl && nomorSpjEl) {
    DetailPenjemputan.setNomorSpj(nomorSpj);
    }
});
