/* ============================================================
   VKTR TCO — Screens 1–5  v1.2
   New screen order:
     Screen 1 — Customer Profile   (unchanged)
     Screen 2 — Vehicle Selection  (was Screen 4)
     Screen 3 — Operation          (was Screen 2; labor removed)
     Screen 4 — Infrastructure     (was Screen 3; unchanged)
     Screen 5 — Financials         (+ inflation slider)
   props: { s (state), set(key,val) }
   ============================================================ */

// ---------- Vehicle catalogue sort (v1.5) ----------
// 6-key sort: engine (EV/ICE) -> brand A-Z -> class A-Z -> GVW desc -> price desc -> efficiency
function sortVehicles(list) {
  return [...list].sort((a, b) => {
    if (a.powertrain !== b.powertrain) return a.powertrain.localeCompare(b.powertrain);
    if (a.brand !== b.brand) return a.brand.localeCompare(b.brand);
    if (a.segment !== b.segment) return a.segment.localeCompare(b.segment);
    if (a.gvw !== b.gvw) return (b.gvw || 0) - (a.gvw || 0);
    if (a.price !== b.price) return (b.price || 0) - (a.price || 0);
    return (a.energyNum || 0) - (b.energyNum || 0);
  });
}
// NOTE: not precomputed at module-load time on purpose -- window.VEHICLES is
// overwritten in place by vehicles.jsx's loadVehiclesReference() (an async
// fetch of data/vehicles.xlsx awaited in app.jsx's boot sequence, AFTER this
// script has already parsed). Computing these once here would freeze a
// catalogue snapshot that predates the xlsx overwrite. Called instead at
// render time (see getSortedVehicles/getDropdownVehicles below) so they
// always reflect the live VEHICLES array.
function getSortedVehicles() { return sortVehicles(VEHICLES); }

// Insert "⚡ EV" / "⛽ ICE" group-header rows ahead of each powertrain block
// for the vehicle-selection dropdown.
function buildDropdownVehicles(list) {
  const out = [];
  let lastPowertrain = null;
  for (const v of list) {
    if (v.powertrain !== lastPowertrain) {
      out.push({
        id: `__header_${v.powertrain}`,
        isHeader: true,
        name: v.powertrain === "EV" ? "⚡ EV" : "⛽ ICE",
      });
      lastPowertrain = v.powertrain;
    }
    out.push(v);
  }
  return out;
}
function getDropdownVehicles() { return buildDropdownVehicles(getSortedVehicles()); }

// Vehicle-type (segment) filter options for the vehicle-selection dropdown,
// shown ahead of the search bar to narrow the candidate list.
const VEHICLE_TYPE_FILTER_OPTIONS = [
  { value: "all", en: "All Types", id: "Semua Tipe" },
  ...["BUS", "LDT", "MDT", "HDT", "TH", "VAN", "Pickup", "Double Cabin"]
    .filter(seg => SEGMENT_LABEL[seg])
    .map(seg => ({ value: seg, en: SEGMENT_LABEL[seg].en, id: SEGMENT_LABEL[seg].id })),
];

// Wheel configuration (axle layout) filter — derived from the vehicle name
// string (e.g. "HOWO V7X HDT 8×4 Swap & Charge" -> "8x4"), not a dedicated
// catalog field: only ~35 of 174 vehicles' names actually state one (mostly
// HDT/TH/some LDT), so this is deliberately a display/filter-only derivation
// rather than a backfilled field — no source data exists to state a wheel
// config for a bus, pickup, or van that doesn't already say so in its name,
// and inventing one would be exactly the kind of fabricated spec this
// codebase's placeholder/estimate flags exist to avoid.
function getWheelConfig(veh) {
  if (!veh || !veh.name) return null;
  const m = veh.name.match(/(\d)[x×](\d)/);
  return m ? `${m[1]}x${m[2]}` : null;
}
const WHEEL_CONFIG_FILTER_OPTIONS = [
  { value: "all", en: "All Wheel Configs", id: "Semua Konfigurasi Roda" },
  ...["4x2", "4x4", "6x2", "6x4", "6x6", "8x2", "8x4"].map(w => ({ value: w, en: w, id: w })),
  { value: "other", en: "Other / Not Stated", id: "Lainnya / Tidak Disebutkan" },
];
function matchesWheelConfigFilter(veh, filter) {
  if (filter === "all") return true;
  const w = getWheelConfig(veh);
  return filter === "other" ? !w : w === filter;
}

// ---------- Maintenance Cost by Year (per vehicle, 5 years) ----------
// v1.7.7 Wave 3: replaces the old top-down "PM Preset" -- these are now the
// real per-year totals from the parts breakdown itself (maintGroupCostForYear),
// not a separate schedule. Clicking a year here selects it in the 6-group
// breakdown table below, so the two are visibly one model, two views.
function MaintenanceYearSummary({ veh, annualKm, tyreTier, tyreTierPriceOverrides, groupOverrides, selectedYear, onSelectYear, lang }) {
  if (!veh) return null;
  const yrs = [1, 2, 3, 4, 5];
  const vals = yrs.map(y => {
    const sums = window.maintGroupCostForYear(veh, annualKm || 50000, tyreTier, tyreTierPriceOverrides, y);
    return Object.keys(sums).reduce((total, g) => total + (groupOverrides?.[g] ?? sums[g] ?? 0), 0);
  });
  return (
    <div className="pm-preset-block">
      <div className="pm-preset-head">
        <span>{tr(lang, "Maintenance Cost by Year", "Biaya Perawatan per Tahun")}</span>
        {/* v1.9.11: was easy to miss as plain text in the note below, and
            got reported as "not scaling with fleet size" -- it's not
            supposed to, this card is one vehicle's own parts-based
            schedule, always was (see the MaintenanceSourceCard comment
            above), but that needs to be unmissable, not just documented. */}
        <Badge kind="warn">{tr(lang, "PER UNIT — not fleet total", "PER UNIT — bukan total armada")}</Badge>
      </div>
      <div className="pm-preset-years">
        {vals.map((v, i) => (
          <div key={i} className={"pm-preset-cell" + (selectedYear === i + 1 ? " active" : "")}
            onClick={onSelectYear ? () => onSelectYear(i + 1) : undefined}
            style={onSelectYear ? { cursor: "pointer" } : undefined}>
            <div className="pm-yr">Y{i + 1}</div>
            <div className="pm-val">{fmt.rpShort(v)}</div>
          </div>
        ))}
      </div>
      <div className="pm-preset-note">
        {tr(lang,
          `Cost for ONE vehicle · ${fmt.num(annualKm || 50000)} km/yr — real year-by-year total from the parts breakdown below (click a year to inspect it there), not a smoothed average. Multiply by Fleet Size (set on the Infrastructure screen) for the fleet-wide figure, which is what the Financial section's FMC bucket already shows.`,
          `Biaya untuk SATU kendaraan · ${fmt.num(annualKm || 50000)} km/thn — total riil per tahun dari rincian komponen di bawah (klik tahun untuk melihat rinciannya), bukan rata-rata yang diratakan. Kalikan dengan Jumlah Armada (diatur di layar Infrastruktur) untuk angka seluruh armada, yang sudah ditampilkan pada kelompok FMC di bagian Finansial.`
        )}
      </div>
    </div>
  );
}

// ---------- Screen 1: Customer Profile ----------
// Fields that are live/computed/transient — never belong in a saved preset's
// "state" snapshot (depot results recompute on load; the screen4 modal flags
// are one-shot UI signals, not durable profile data).
const PRESET_SAVE_EXCLUDE_KEYS = [
  "depotBom", "depotMetrics", "depotBomInclude",
  "_schemaVersion",
];

function slugify(text) {
  return (text || "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/(^_|_$)/g, "");
}

function downloadJsonFile(obj, filename) {
  const blob = new Blob([JSON.stringify(obj, null, 2)], { type: "application/json" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = filename;
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 4000);
}

// Save/overwrite current profile as a loadable reference preset. Presets are
// static JSON files under presets/<folder>/ regenerated into presets.js by
// build_presets.py — a browser page can't write to that folder directly, so
// "saving" means downloading the exact file that belongs there; placing it
// (new file, or overwriting one with the same id) and re-running the build
// script is what actually publishes it.
function SaveAsPresetCard({ s, lang, referencePresets, presetFolders }) {
  const [mode, setMode] = useState("new"); // "new" | "overwrite"
  const [overwriteId, setOverwriteId] = useState("");
  const [folder, setFolder] = useState(presetFolders[0] || "");
  const [id, setId] = useState(slugify(s.company) || "my_preset");
  const [labelEn, setLabelEn] = useState(s.company || "");
  const [labelId, setLabelId] = useState(s.company || "");
  const [source, setSource] = useState("User-saved preset");
  const [descEn, setDescEn] = useState(s.notes || "");
  const [descId, setDescId] = useState(s.notes || "");

  const applyOverwriteSelection = (presetId) => {
    setOverwriteId(presetId);
    const p = referencePresets.find(x => x.id === presetId);
    if (!p) return;
    setFolder(p.folder || "");
    setId(p.id);
    setLabelEn(p.label.en); setLabelId(p.label.id);
    setSource(p.source || "");
    setDescEn(p.desc.en); setDescId(p.desc.id);
  };

  const handleSave = () => {
    const finalFolder = (folder || "Other").trim();
    const finalId = slugify(id) || slugify(labelEn) || "preset";
    const cleanState = { ...s };
    PRESET_SAVE_EXCLUDE_KEYS.forEach(k => delete cleanState[k]);
    const preset = {
      id: finalId,
      label: { en: labelEn || finalId, id: labelId || labelEn || finalId },
      source: source || "User-saved preset",
      desc: { en: descEn || "", id: descId || descEn || "" },
      state: cleanState,
    };
    downloadJsonFile(preset, `${finalId}.json`);
    window.alert(tr(lang,
      `Downloaded "${finalId}.json". To publish it: place this file in presets/${finalFolder}/ (create the folder if it doesn't exist yet), then run "python build_presets.py" and refresh this page.`,
      `"${finalId}.json" terunduh. Untuk menerbitkannya: letakkan file ini di presets/${finalFolder}/ (buat folder jika belum ada), lalu jalankan "python build_presets.py" dan muat ulang halaman ini.`));
  };

  return (
    <Card title="Save / Overwrite as Preset" idSub="Simpan / Timpa sebagai Preset"
      head={<InfoHint note={tr(lang,
        "Saves the current profile as a downloadable preset file. Presets are plain JSON files under presets/<folder>/ — this button downloads the exact file; placing it there and re-running \"python build_presets.py\" is what actually adds it to the loadable list above.",
        "Menyimpan profil saat ini sebagai file preset yang dapat diunduh. Preset adalah file JSON biasa di bawah presets/<folder>/ — tombol ini mengunduh file yang tepat; meletakkannya di sana dan menjalankan ulang \"python build_presets.py\" yang sebenarnya menambahkannya ke daftar yang dapat dimuat di atas.")} />}>
      <div className="grid-2">
        <Field en="Mode" id="Mode">
          <Select value={mode} onChange={setMode} options={[
            { value: "new", label: tr(lang, "Save as new preset", "Simpan sebagai preset baru") },
            { value: "overwrite", label: tr(lang, "Overwrite existing preset", "Timpa preset yang ada") },
          ]} />
        </Field>
        {mode === "overwrite" && (
          <Field en="Existing Preset" id="Preset yang Ada">
            <Select value={overwriteId} onChange={applyOverwriteSelection} options={referencePresets.map(p => ({ value: p.id, label: `${p.folder ? p.folder + " / " : ""}${tr(lang, p.label.en, p.label.id)}` }))} placeholder={tr(lang, "Choose a preset to overwrite...", "Pilih preset yang akan ditimpa...")} />
          </Field>
        )}
        <Field en="Folder" id="Folder">
          <TextInput value={folder} onChange={setFolder} placeholder="e.g. MINING_PT. Customer Name" />
        </Field>
        <Field en="Preset ID" id="ID Preset">
          <TextInput value={id} onChange={setId} placeholder="e.g. mining_my_scenario" />
        </Field>
        <Field en="Label (EN)" id="Label (EN)">
          <TextInput value={labelEn} onChange={setLabelEn} />
        </Field>
        <Field en="Label (ID)" id="Label (ID)">
          <TextInput value={labelId} onChange={setLabelId} />
        </Field>
        <Field en="Source" id="Sumber">
          <TextInput value={source} onChange={setSource} />
        </Field>
        <Field en="Description (EN)" id="Deskripsi (EN)" full>
          <TextArea value={descEn} onChange={setDescEn} rows={2} />
        </Field>
        <Field en="Description (ID)" id="Deskripsi (ID)" full>
          <TextArea value={descId} onChange={setDescId} rows={2} />
        </Field>
      </div>
      <button type="button" className="btn btn-primary" style={{ marginTop: 12 }} onClick={handleSave}>
        {mode === "overwrite"
          ? <Tr en="Download Overwrite File" id="Unduh File Timpa" />
          : <Tr en="Download New Preset File" id="Unduh File Preset Baru" />}
      </button>
    </Card>
  );
}

// ---- Shared Presets browser (v1.7) — admin-curated, cloud-hosted presets,
// separate from the static Excel-validated ones above. Public-read, so this
// shows for every user (guest or signed-in); editing lives in the Admin Panel.
function SharedPresetsCard({ loadPresetData, lang }) {
  const { presets } = window.useSharedPresets ? window.useSharedPresets() : { presets: {} };
  const ids = presets ? Object.keys(presets) : [];
  if (!presets || ids.length === 0) return null;

  const handleLoad = (data) => {
    const proceed = window.confirm(tr(lang,
      `Load "${data.name}"? This replaces your current profile on this device. Export first if you want to keep it.`,
      `Muat "${data.name}"? Ini akan mengganti profil Anda saat ini di perangkat ini. Ekspor dulu jika ingin menyimpannya.`));
    if (proceed) loadPresetData(data.state);
  };

  return (
    <Card>
      <CollapsibleSection title={"☁ " + tr(lang, "Shared Presets (optional)", "Preset Bersama (opsional)")} defaultOpen={false}>
        <div className="option-card-grid">
          {ids.map((id) => {
            const data = presets[id];
            return (
              <div key={id} className="option-card" onClick={() => handleLoad(data)} style={{ cursor: "pointer" }}>
                <div className="oc-icon">☁</div>
                <div className="oc-label">{data.name}</div>
                {data.description && <div className="oc-desc">{data.description}</div>}
              </div>
            );
          })}
        </div>
      </CollapsibleSection>
    </Card>
  );
}

function Screen1({ s, set, goTo, loadPreset, loadPresetData, onOpenNeedsIntake }) {
  const { lang } = useLang();
  const ecosystemSelectOptions = ECOSYSTEM_OPTIONS.map(e => ({
    value: e.id, label: `${e.icon} ${tr(lang, e.label, e.labelId)}`,
  }));

  const referencePresets = window.TCO_REFERENCE_PRESETS || [];
  const presetFolders = Array.from(new Set(referencePresets.map(p => p.folder || "Other")));
  const [presetFolder, setPresetFolder] = useState(presetFolders[0] || null);
  const visiblePresets = referencePresets.filter(p => (p.folder || "Other") === presetFolder);
  // Whether the live File System Access Explorer is actually showing real
  // folder content right now -- once it is, the static card list below it
  // is a confusing duplicate of the same data in an older style, not a
  // useful fallback anymore, so it gets hidden rather than shown alongside.
  const [explorerGranted, setExplorerGranted] = useState(false);
  const handleLoadPreset = (preset) => {
    const proceed = window.confirm(tr(lang,
      `Load "${preset.label.en}"? This replaces your current profile on this device. Export first if you want to keep it.`,
      `Muat "${preset.label.id}"? Ini akan mengganti profil Anda saat ini di perangkat ini. Ekspor dulu jika ingin menyimpannya.`));
    if (proceed && loadPreset) loadPreset(preset.id);
  };

  return (
    <>
    {onOpenNeedsIntake && (
      <Card>
        <div className="needs-intake-entry">
          <div className="nie-text">
            {tr(lang, "🧭 Not sure where to start?", "🧭 Belum tahu harus mulai dari mana?")}
            <small>{tr(lang, "Answer 5 quick questions and we'll set up your profile.", "Jawab 5 pertanyaan singkat dan kami akan menyiapkan profil Anda.")}</small>
          </div>
          <button className="btn btn-accent" onClick={onOpenNeedsIntake}>
            {tr(lang, "Quick Needs Assessment", "Penilaian Kebutuhan Cepat")}
          </button>
        </div>
      </Card>
    )}
    {referencePresets.length > 0 && (
      <Card>
        {/* Hidden by default — presets are optional and were crowding out the
            user's own profile entry above the fold; one click reveals them
            for whoever wants a starting point or is unsure how to begin. */}
        <CollapsibleSection title={"📁 " + tr(lang, "Presets (optional)", "Preset (opsional)")} defaultOpen={false}>
          <div style={{ marginBottom: explorerGranted ? 0 : 18 }}>
            {!explorerGranted && (
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6 }}>
                {tr(lang, "Manage presets folder", "Kelola folder preset")}
              </div>
            )}
            <PresetExplorer lang={lang} loadPreset={loadPresetData} onGrantedChange={setExplorerGranted} />
          </div>
          {!explorerGranted && (
            <>
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6, marginTop: 18 }}>
                {tr(lang, "Quick reference list", "Daftar referensi cepat")}
                <InfoHint note={tr(lang,
                  "Presets are grouped by folder (Industry_Company). Pick a folder, then a preset within it — each replays a real internal scenario through this platform's own engine, validated against real TCOO data.",
                  "Preset dikelompokkan per folder (Industri_Perusahaan). Pilih folder, lalu preset di dalamnya — setiap preset memutar ulang skenario nyata melalui mesin platform ini, tervalidasi terhadap data TCOO nyata.")} />
              </div>
              <div className="option-card-grid" style={{ marginBottom: 14 }}>
                {presetFolders.map(folder => (
                  <div key={folder}
                    className={"option-card" + (presetFolder === folder ? " active" : "")}
                    onClick={() => setPresetFolder(folder)} style={{ cursor: "pointer" }}>
                    <div className="oc-icon">📁</div>
                    <div className="oc-label">{folder}</div>
                    <div className="oc-desc">{tr(lang, `${referencePresets.filter(p => (p.folder || "Other") === folder).length} preset(s)`, `${referencePresets.filter(p => (p.folder || "Other") === folder).length} preset`)}</div>
                  </div>
                ))}
              </div>
              <div className="option-card-grid">
                {visiblePresets.map(p => (
                  <div key={p.id} className="option-card" onClick={() => handleLoadPreset(p)} style={{ cursor: "pointer" }}>
                    <div className="oc-icon">📊</div>
                    <div className="oc-label">{tr(lang, p.label.en, p.label.id)}</div>
                    <div className="oc-desc">{tr(lang, p.desc.en, p.desc.id)}</div>
                    <div className="oc-desc" style={{ fontStyle: "italic", marginTop: 4 }}>{p.source}</div>
                  </div>
                ))}
              </div>
            </>
          )}
          <div style={{ marginTop: 14 }}>
            <SaveAsPresetCard s={s} lang={lang} referencePresets={referencePresets} presetFolders={presetFolders} />
          </div>
        </CollapsibleSection>
      </Card>
    )}
    <SharedPresetsCard loadPresetData={loadPresetData} lang={lang} />
    <Card title="Customer Profile" idSub="Profil Pelanggan" head={<ResetScreenButton s={s} set={set} screenKey="screen1" />}>
      <div className="grid-2">
        <Field en="Company Name" id="Nama Perusahaan" req>
          <TextInput value={s.company} onChange={v => set("company", v)} placeholder="e.g. PT. Transportasi Nusantara" />
        </Field>
        <Field en="Contact Person" id="Nama PIC">
          <TextInput value={s.contact} onChange={v => set("contact", v)} placeholder="e.g. Budi Santoso" />
        </Field>
        <Field en="Industry" id="Industri">
          <Select value={s.ecosystemId} onChange={v => set("ecosystemId", v)} options={ecosystemSelectOptions} placeholder="Pilih industri..." />
        </Field>
        <Field en="City" id="Kota">
          <TextInput value={s.city} onChange={v => set("city", v)} placeholder="e.g. Jakarta" />
        </Field>
        <Field en="Notes" id="Catatan" full>
          <TextArea value={s.notes} onChange={v => set("notes", v)} placeholder="Konteks tambahan..." rows={3} />
        </Field>
      </div>

      <div className="grid-2" style={{ marginTop: 18 }}>
        <Field en="Approximate Fleet Size" id="Estimasi Jumlah Armada" opt
          helpEn="Pre-fills the Fleet Size on the Infrastructure screen (Tab 1)."
          help="Mengisi otomatis Jumlah Armada di layar Infrastruktur (Tab 1).">
          <AffixInput
            value={s.approxFleetSize != null ? fmt.num(s.approxFleetSize) : ""}
            suffix={tr(lang, "vehicles", "kendaraan")}
            onChange={v => {
              const cleaned = v.replace(/\D/g, "");
              set("approxFleetSize", cleaned === "" ? null : Number(cleaned));
            }}
          />
        </Field>
        <Field en="Target Project Start Date" id="Target Tanggal Mulai Proyek" opt
          helpEn="Month and year are sufficient. Used for the deployment timeline and NPV discounting start point."
          help="Cukup bulan dan tahun. Digunakan untuk linimasa deployment dan titik awal diskonto NPV.">
          <input
            type="month"
            className="input"
            value={s.projectStartDate || ""}
            onChange={e => set("projectStartDate", e.target.value || null)}
          />
        </Field>
      </div>

      <div className="row-full" style={{ marginTop: 18, display: "flex", alignItems: "center" }}>
        <ExpertToggle
          isOn={!!s.costModelFlat}
          onToggle={(v) => set("costModelFlat", v)}
          labelEn="Use flat baseline cost model"
          labelId="Gunakan model biaya dasar rata"
          icon="⚖️"
        />
        <InfoHint note={tr(lang,
          "When on, all infrastructure CAPEX/OPEX and financial presets ignore the selected industry's multipliers and use the neutral \"Others\" baseline instead — useful for an industry-agnostic, fully user-editable estimate.",
          "Saat aktif, semua CAPEX/OPEX infrastruktur dan preset keuangan mengabaikan pengganda industri yang dipilih dan menggunakan baseline \"Lainnya\" yang netral — berguna untuk estimasi yang tidak terikat industri dan dapat diedit penuh.")} />
      </div>
    </Card>
    </>
  );
}

// ---------- Screen 2: Vehicle Selection (was Screen 4) ----------
function VehicleCard({ en, id, vehKey, priceKey, overrideKey, overrideBasisKey, overrideCycleKey, energyOverrideKey, s, set }) {
  const { lang } = useLang();
  const veh = findVeh(s[vehKey]);
  const [typeFilter, setTypeFilter] = useState("all");
  const [wheelConfigFilter, setWheelConfigFilter] = useState("all");
  const payloadBuildKey = vehKey === "vehA" ? "payloadBuildA" : "payloadBuildB";

  // Narrow the dropdown to the chosen vehicle type and/or wheel config. "all"
  // on either restores the full catalogue for that dimension.
  const dropdownItems = (typeFilter === "all" && wheelConfigFilter === "all")
    ? getDropdownVehicles()
    : buildDropdownVehicles(getSortedVehicles().filter(v =>
        (typeFilter === "all" || v.segment === typeFilter) &&
        matchesWheelConfigFilter(v, wheelConfigFilter)
      ));

  const typeFilterOptions = VEHICLE_TYPE_FILTER_OPTIONS.map(o => ({ value: o.value, label: tr(lang, o.en, o.id) }));
  const wheelConfigFilterOptions = WHEEL_CONFIG_FILTER_OPTIONS.map(o => ({ value: o.value, label: tr(lang, o.en, o.id) }));

  // Changing either filter to something that excludes the currently selected
  // vehicle clears the selection, so the search bar only ever offers
  // vehicles matching both active filters.
  const clearSelection = () => {
    set(vehKey, null);
    set(priceKey, null);
    set(overrideKey, null);
    set(overrideCycleKey, null);
    set(energyOverrideKey, null);
    if (veh && veh.powertrain === "EV") {
      set("ecEmptyOverride", null);
      set("ecFullOverride", null);
    }
  };
  const handleTypeFilterChange = (newFilter) => {
    setTypeFilter(newFilter);
    if (newFilter !== "all" && veh && veh.segment !== newFilter) clearSelection();
  };
  const handleWheelConfigFilterChange = (newFilter) => {
    setWheelConfigFilter(newFilter);
    if (veh && !matchesWheelConfigFilter(veh, newFilter)) clearSelection();
  };

  const renderItem = (v) => {
    const segLabel = SEGMENT_LABEL[v.segment]?.[lang] || v.segment;
    const wheelConfig = getWheelConfig(v);
    return (
      <>
        <div className="di-top">
          <Badge kind={v.powertrain === "EV" ? "ev" : "ice"}>{v.powertrain}</Badge>
          <span className="di-brand">{v.brand}</span>
          {v.vktr && <Badge kind="vktr">VKTR</Badge>}
          {v.placeholder && <Badge kind="warn">⚠</Badge>}
          <span className="di-name">{v.name}</span>
        </div>
        <div className="di-sub">
          {segLabel}{wheelConfig ? ` (${wheelConfig})` : ""} · GVW {v.gvw ? fmt.num(v.gvw) + " kg" : "—"} · {fmt.rpShort(v.price)}
        </div>
      </>
    );
  };

  const pickVeh = (newId) => {
    // v1.9.3 fix: s.ecEmptyOverride/ecFullOverride (Screen 4 Expert Mode)
    // are keyed to "the EV in this comparison" (computeEcActual, data.jsx),
    // NOT to vehicle slot A/B -- unlike overrideKey/energyOverrideKey above,
    // pickVeh previously left them untouched. Swapping which vehicle is the
    // EV (or picking a different EV) then silently fed a stale override
    // tuned for the OLD EV's battery/EC into the new vehicle's range (VR)
    // calc, which could zero out VR and made Annual Mileage/Charging
    // Strategy get stuck on "Not Yet Computed" no matter what the user
    // typed -- reported by Rija 2026-07-17. Clear whenever the EV identity
    // in this slot could change, either direction.
    const wasEv = veh && veh.powertrain === "EV";
    const newVeh = findVeh(newId);
    const willBeEv = newVeh && newVeh.powertrain === "EV";
    set(vehKey, newId);
    if (newVeh) set(priceKey, newVeh.price);
    set(overrideKey, null);
    set(overrideCycleKey, null);
    set(energyOverrideKey, null);
    set(payloadBuildKey, false);
    if (wasEv || willBeEv) {
      set("ecEmptyOverride", null);
      set("ecFullOverride", null);
    }
  };

  return (
    <div className="veh-col">
      <Card>
        <div className="veh-head">
          {lang === "en" ? en : id}
        </div>
        <div className="veh-type-filter" style={{ display: "flex", gap: 8 }}>
          <Select
            value={typeFilter}
            onChange={handleTypeFilterChange}
            options={typeFilterOptions}
          />
          <Select
            value={wheelConfigFilter}
            onChange={handleWheelConfigFilterChange}
            options={wheelConfigFilterOptions}
          />
        </div>
        <SearchableDropdown
          value={s[vehKey]}
          onChange={pickVeh}
          items={dropdownItems}
          placeholder={tr(lang, "Search vehicle (brand, name)...", "Cari kendaraan (merek, nama)...")}
          renderItem={renderItem}
        />

        {veh && (
          <>
            {/* Badges + name */}
            <div style={{ display: "flex", gap: 6, marginTop: 14, alignItems: "center", flexWrap: "wrap" }}>
              <Badge kind={veh.powertrain === "EV" ? "ev" : "ice"}>{veh.powertrain}</Badge>
              {veh.vktr && <Badge kind="vktr">VKTR</Badge>}
              {veh.placeholder && <Badge kind="warn">⚠ Data Estimasi</Badge>}
              {(!veh.placeholder && veh.pmEstimate) && <Badge kind="warn">⚠ PM Estimasi</Badge>}
              <span style={{ fontWeight: 700, fontSize: 15 }}>{veh.name}</span>
            </div>

            {/* Spec grid */}
            <div className="spec-grid">
              <div className="spec-cell">
                <div className="sk">{tr(lang, "GVW", "GVW")}{veh.gvwEst ? " ⚠" : ""}</div>
                <div className="sv">{veh.gvw ? fmt.num(veh.gvw) + " kg" : "—"}</div>
              </div>
              {veh.power != null && (
                <div className="spec-cell">
                  <div className="sk">{veh.powertrain === "EV" ? tr(lang, "Motor Power", "Daya Motor") : tr(lang, "Engine Power", "Daya Mesin")}</div>
                  <div className="sv">{veh.power} kW</div>
                </div>
              )}
              {veh.batteryKwh != null && (
                <div className="spec-cell">
                  <div className="sk">{tr(lang, "Battery", "Baterai")}</div>
                  <div className="sv">{veh.batteryKwh} kWh</div>
                </div>
              )}
              <div className="spec-cell editable">
                <div className="sk">
                  {tr(lang, "Buying Price", "Harga Beli")}
                  <span className="edit-tag">{tr(lang, "editable", "dapat diubah")}</span>
                </div>
                <div className="affix price-affix">
                  <span className="fix pre">Rp</span>
                  <input
                    value={fmt.num(s[priceKey] ?? veh.price)}
                    inputMode="numeric"
                    onChange={e => set(priceKey, Number(e.target.value.replace(/\D/g, "")) || 0)}
                  />
                </div>
              </div>
              <div className="spec-cell editable">
                <div className="sk">
                  {veh.powertrain === "EV" ? tr(lang, "Energy", "Energi") : tr(lang, "Fuel", "Bahan Bakar")}
                  <span className="edit-tag">{tr(lang, "editable", "dapat diubah")}</span>
                </div>
                <div className="affix price-affix">
                  <input
                    value={fmt.num(s[energyOverrideKey] ?? veh.energyNum)}
                    inputMode="decimal"
                    onChange={e => { const n = Number(e.target.value.replace(/[^\d.]/g, "")); set(energyOverrideKey, isNaN(n) ? null : n); }}
                  />
                  <span className="fix suf">{veh.powertrain === "EV" ? "kWh/km" : "L/100km"}</span>
                </div>
                <ValueFlag defaultValue={veh.energyNum} currentValue={s[energyOverrideKey] ?? null}
                  onReset={() => set(energyOverrideKey, null)}
                  label={tr(lang, "Catalog default — real-world consumption can differ by route/load", "Default katalog — konsumsi nyata dapat berbeda menurut rute/muatan")} />
              </div>
            </div>

            {/* Payload Build (Lead Time module, v1.7.6) — informational only, does not affect TCO */}
            {window.isPayloadBuildToggleable(veh.id) && (
              <div style={{ marginTop: 12 }}>
                <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                  <input type="checkbox" checked={!!s[payloadBuildKey]}
                    onChange={e => set(payloadBuildKey, e.target.checked)} />
                  {tr(lang, "Payload Build (attachment fitted)", "Payload Build (attachment terpasang)")}
                  <InfoHint note={tr(lang,
                    "Adds a dump/box/wing-box attachment build step at VKTS — about 60 extra days of lead time. Off by default (base lead time assumes a bare cabin chassis). The attachment's own cost isn't modeled here; adjust this vehicle's Buying Price above manually if you want to reflect it.",
                    "Menambahkan tahap pemasangan attachment (dump/box/wing-box) di VKTS — sekitar 60 hari tambahan lead time. Nonaktif secara default (lead time dasar mengasumsikan kabin chasis kosong). Biaya attachment tidak dimodelkan di sini; sesuaikan Harga Beli kendaraan ini di atas secara manual jika ingin merefleksikannya.")} />
                </label>
              </div>
            )}
            {window.LEAD_TIME_VKTR?.[veh.id]?.payloadBuildFixedIncluded && (
              <div style={{ marginTop: 12, fontSize: 13, color: "var(--c-muted)" }}>
                {tr(lang, "Payload Build: Included (this SKU ships fully bodied)", "Payload Build: Termasuk (SKU ini dikirim dalam kondisi sudah berbadan)")}
              </div>
            )}

            {/* Data quality warning */}
            {veh.placeholder && (
              <div style={{ marginTop: 12 }}>
                <WarnHint label={tr(lang, "Estimated Data", "Data Estimasi")}
                  note={tr(lang,
                    "Specification data for this vehicle is estimated and has not been confirmed by the OEM. Verify before final analysis.",
                    "Data spesifikasi kendaraan ini adalah estimasi dan belum dikonfirmasi OEM. Verifikasi sebelum analisis final.")} />
              </div>
            )}
          </>
        )}
      </Card>
    </div>
  );
}

// ---------- Screen 2 — Maintenance Parts Breakdown sub-tab ----------
const MAINTENANCE_GROUP_ORDER = ["tyre", "brake", "battery_fuel", "cooling", "powertrain", "other"];

function TyreTierToggle({ tyreTier, tyreTierPriceOverrides, onTierChange, onPriceChange, lang }) {
  return (
    <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap", margin: "8px 0 14px" }}>
      <span style={{ fontSize: 12, fontWeight: 600, color: "var(--text-muted)" }}>
        {tr(lang, "Tyre tier:", "Tingkat ban:")}
      </span>
      {Object.entries(window.TYRE_TIERS).map(([key, tier]) => {
        const active = tyreTier === key;
        const priceVal = tyreTierPriceOverrides?.[key] ?? tier.price;
        return (
          <div key={key} style={{ display: "flex", alignItems: "center", gap: 6 }}>
            <button
              type="button"
              className={active ? "chip chip-active" : "chip"}
              onClick={() => onTierChange(active ? null : key)}
              title={tr(lang, tier.examplesEn, tier.examplesId)}
            >
              {tr(lang, tier.nameEn, tier.nameId)}
            </button>
            {active && (
              <input
                className="input num-sync"
                style={{ width: 110 }}
                type="number"
                value={priceVal}
                onChange={e => onPriceChange(key, Number(e.target.value) || 0)}
              />
            )}
          </div>
        );
      })}
      {tyreTier && (
        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>
          {tr(lang, window.TYRE_TIERS[tyreTier].examplesEn, window.TYRE_TIERS[tyreTier].examplesId)}
        </span>
      )}
      {!tyreTier && (
        <span style={{ fontSize: 11, color: "var(--text-muted)" }}>
          {tr(lang, "Default — uses the template's researched tyre price below", "Default — menggunakan harga ban hasil riset di template di bawah")}
        </span>
      )}
    </div>
  );
}

function MaintenanceBreakdownTable({ veh, annualKm, lang, tyreTier, tyreTierPriceOverrides, onTierChange, onPriceChange, groupOverrides, onGroupOverrideChange, s, overrideKey, selectedYear }) {
  if (!veh) return null;
  const year = selectedYear || 1;
  const defaults = window.maintGroupCostForYear(veh, annualKm, tyreTier, tyreTierPriceOverrides, year);
  const total = MAINTENANCE_GROUP_ORDER.reduce((sum, g) => sum + (groupOverrides?.[g] ?? defaults[g] ?? 0), 0);
  // Same priority-mode logic as the source card's badge above (§2.4) --
  // wired here too so this table's own text is never wrong about whether
  // ITS number is the one actually feeding TCO. A manual override wins
  // over this breakdown; otherwise this IS the TCO source (no competing
  // top-down mode anymore, v1.7.7 Wave 3).
  const mode = maintActiveModeFor(s, overrideKey);
  const isActive = mode.en === "Parts-based estimate";

  return (
    <div className="veh-col">
      <Card>
        <div className="veh-head" style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
          <span><Badge kind={veh.powertrain === "EV" ? "ev" : "ice"}>{veh.powertrain}</Badge>{" "}{veh.name}</span>
          <Badge kind={isActive ? "vktr" : "muted"}>
            {isActive ? tr(lang, "Active in TCO", "Aktif di TCO") : tr(lang, "Not active", "Tidak aktif")}
          </Badge>
        </div>
        <div style={{ fontSize: 12, color: "var(--text-muted)", margin: "8px 0 12px", display: "flex", alignItems: "center" }}>
          {tr(lang, `Year ${year} estimate`, `Estimasi Tahun ${year}`)}
          <InfoHint note={tr(lang,
            `Estimated cost per maintenance aspect for Year ${year}, defaulted from a generic ${veh.powertrain} parts template (scaled by GVW vs. a reference vehicle). Figures vary year to year as parts hit their replacement interval — see the summary above. Edit any figure directly to set a flat annual override for that group (applies to every year).`,
            `Estimasi biaya per aspek perawatan untuk Tahun ${year}, default dari template suku cadang generik ${veh.powertrain} (diskalakan berdasarkan GVW terhadap kendaraan referensi). Angka bervariasi tiap tahun sesuai interval penggantian komponen — lihat ringkasan di atas. Edit langsung untuk mengatur override tahunan tetap per kelompok (berlaku di semua tahun).`)} />
        </div>

        {!isActive && (
          <div style={{ marginBottom: 8 }}>
            <WarnHint label={tr(lang, "Override Active", "Override Aktif")}
              note={tr(lang,
                "A manual maintenance override is set for this vehicle above — it wins over this breakdown. This table's numbers still feed nothing into TCO until the override is cleared.",
                "Override perawatan manual sudah diatur untuk kendaraan ini di atas — selalu didahulukan dari rincian ini. Angka tabel ini belum masuk ke TCO selama override belum dihapus.")} />
          </div>
        )}

        <TyreTierToggle tyreTier={tyreTier} tyreTierPriceOverrides={tyreTierPriceOverrides}
          onTierChange={onTierChange} onPriceChange={onPriceChange} lang={lang} />

        <table className={"sbs-table" + (isActive ? "" : " maint-inactive")}>
          <tbody>
            {MAINTENANCE_GROUP_ORDER.filter(g => defaults[g] != null || groupOverrides?.[g] != null).map(g => {
              const group = window.MAINTENANCE_GROUPS[g];
              const value = groupOverrides?.[g] ?? defaults[g] ?? 0;
              return (
                <tr key={g}>
                  <td className="lbl">
                    {group.icon} {tr(lang, group.nameEn, group.nameId)}
                  </td>
                  <td className="num">
                    <span className="inline-price-edit">
                      <span className="fix pre">Rp</span>
                      <input className="inline-price-input" value={fmt.num(Math.round(value))}
                        onChange={e => { const n = Number(e.target.value.replace(/\D/g, "")); onGroupOverrideChange(g, isNaN(n) ? null : n); }} />
                      <ValueFlag defaultValue={Math.round(defaults[g] ?? 0)} currentValue={groupOverrides?.[g] ?? null}
                        onReset={() => onGroupOverrideChange(g, null)} />
                    </span>
                    {" "}/{tr(lang, "yr", "thn")}
                  </td>
                </tr>
              );
            })}
            <tr className="total">
              <td className="lbl">{tr(lang, `Total — Year ${year}`, `Total — Tahun ${year}`)}</td>
              <td className="num">{fmt.rp(total)}</td>
            </tr>
          </tbody>
        </table>
        <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 10 }}>
          {tr(lang,
            `Currently active for TCO: ${mode.en.toLowerCase()}.`,
            `Saat ini aktif untuk TCO: ${mode.id.toLowerCase()}.`)}
        </div>
      </Card>
    </div>
  );
}

// Which of the 2 maintenance-cost priority modes (CALCULATION_ENGINE.md §2.4)
// is actually active for a given vehicle slot right now. v1.7.7 Wave 3:
// the top-down PM schedule is gone -- the parts breakdown is the sole
// computed source, an override is the only thing that can beat it.
function maintActiveModeFor(s, overrideKey) {
  return (s && s[overrideKey] != null)
    ? { en: "Manual override", id: "Override manual", badge: "warn" }
    : { en: "Parts-based estimate", id: "Estimasi berbasis komponen", badge: "vktr" };
}

// Per-vehicle PM Preset preview + manual override (v1.7.7: moved here from
// Vehicle Selection -- previously this rendered right on the vehicle card,
// implying it was THE maintenance number, while the actual active source
// (this toggle, the override, or the breakdown below) lived on this tab
// instead, invisibly to anyone only looking at Vehicle Selection.
function MaintenanceSourceCard({ en, id, veh, annualKm, terrain, overrideKey, overrideBasisKey, overrideCycleKey, s, set, lang, tyreTier, tyreTierPriceOverrides, groupOverrides, selectedYear, onSelectYear }) {
  if (!veh) return null;
  const mode = maintActiveModeFor(s, overrideKey);
  return (
    <div className="veh-col">
      <Card>
        <div className="veh-head" style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
          <span>{lang === "en" ? en : id}</span>
          <Badge kind={mode.badge}>{tr(lang, mode.en, mode.id)}</Badge>
        </div>

        <MaintenanceYearSummary veh={veh} annualKm={annualKm} tyreTier={tyreTier} tyreTierPriceOverrides={tyreTierPriceOverrides}
          groupOverrides={groupOverrides} selectedYear={selectedYear} onSelectYear={onSelectYear} lang={lang} />

        <div style={{ marginTop: 14 }}>
          <Field
            en="Override Maintenance Cost"
            id="Override Biaya Perawatan"
            opt
            helpEn="Leave blank to use the parts-based breakdown below (shown by year above). Enter a value to override for all years — an override always wins."
            help="Biarkan kosong untuk pakai rincian berbasis komponen di bawah (ditampilkan per tahun di atas). Isi nilai untuk override semua tahun — override selalu didahulukan.">
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              <AffixInput
                value={s[overrideKey] != null ? fmt.num(s[overrideKey]) : ""}
                prefix="Rp"
                suffix={
                  s[overrideBasisKey] === "month" ? "/bln"
                  : s[overrideBasisKey] === "perkm" ? "/siklus"
                  : "/thn"
                }
                placeholder={tr(lang, "No override", "Tanpa override")}
                onChange={v => {
                  const cleaned = v.replace(/\D/g, "");
                  set(overrideKey, cleaned === "" ? null : Number(cleaned));
                }}
              />
              <Select
                value={s[overrideBasisKey]}
                onChange={v => set(overrideBasisKey, v)}
                options={[
                  { value: "year",  label: tr(lang, "Per year", "Per tahun")  },
                  { value: "month", label: tr(lang, "Per month", "Per bulan") },
                  { value: "perkm", label: tr(lang, "Per km cycle", "Per siklus km") },
                ]}
              />
            </div>
            {s[overrideBasisKey] === "perkm" && (
              <div style={{ marginTop: 8 }}>
                <AffixInput
                  value={s[overrideCycleKey] != null ? fmt.num(s[overrideCycleKey]) : ""}
                  suffix="km/siklus"
                  onChange={v => {
                    const cleaned = v.replace(/\D/g, "");
                    set(overrideCycleKey, cleaned === "" ? null : Number(cleaned));
                  }}
                />
                <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 4, display: "flex", alignItems: "center" }}>
                  {tr(lang, "Service cycle", "Siklus servis")}
                  <InfoHint note={tr(lang,
                    "Distance interval between maintenance services (e.g. 10,000 km). Cost above is charged per service, scaled by annual mileage ÷ this cycle.",
                    "Jarak antar servis perawatan (mis. 10.000 km). Biaya di atas dikenakan per servis, diskalakan sesuai jarak tempuh tahunan ÷ siklus ini.")} />
                </div>
              </div>
            )}
          </Field>
        </div>
      </Card>
    </div>
  );
}

function MaintenanceBreakdownPanel({ s, set, lang }) {
  const vehA = window.findVeh(s.vehA);
  const vehB = window.findVeh(s.vehB);
  const [selectedYear, setSelectedYear] = useState(1);
  const setTierPrice = (key, val) => set("tyreTierPriceOverrides", { ...(s.tyreTierPriceOverrides || {}), [key]: val });
  const setGroupOverride = (vehSlot, group, val) => {
    const next = { ...(s.maintGroupOverrides || {}) };
    const slotNext = { ...(next[vehSlot] || {}) };
    if (val == null) delete slotNext[group]; else slotNext[group] = val;
    next[vehSlot] = slotNext;
    set("maintGroupOverrides", next);
  };
  return (
    <>
      <Card>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {tr(lang,
            "One model: the 6-aspect parts breakdown below is the maintenance cost source for TCO — the year cards above each vehicle are its real per-year totals, not a separate schedule.",
            "Satu model: rincian 6 aspek komponen di bawah adalah sumber biaya perawatan untuk TCO — kartu tahun di atas tiap kendaraan adalah total riil per tahunnya, bukan jadwal terpisah.")}
          <InfoHint note={tr(lang,
            "This estimates periodic maintenance cost across 6 fixed aspects (Tyre, Brake system, Battery/fuel system, Cooling system, Powertrain system, Others/unscheduled), each defaulted from a generic EV/ICE parts template (BYD electric-bus PM schedule and Hino/Isuzu diesel PM items, Indonesian aftermarket pricing researched June 2026) scaled by the selected vehicle's GVW, and evaluated per year (a part due for replacement in year 3 shows as a real cost spike in year 3, not smoothed across every year). Each aspect's Rp figure is directly editable — overwrite it if you have a better number for that vehicle; an edited figure applies as a flat annual override across every year.",
            "Ini mengestimasi biaya perawatan berkala dalam 6 aspek tetap (Ban, Sistem Rem, Sistem Baterai/BBM, Sistem Pendingin, Sistem Penggerak, Lainnya/Tak Terjadwal), masing-masing default dari template suku cadang generik EV/ICE (jadwal PM bus listrik BYD dan item PM diesel Hino/Isuzu, harga pasar Indonesia hasil riset Juni 2026) yang diskalakan berdasarkan GVW kendaraan terpilih, dan dihitung per tahun (komponen yang jatuh tempo di tahun ke-3 muncul sebagai lonjakan biaya riil di tahun itu, bukan diratakan di semua tahun). Angka Rp tiap aspek dapat diedit langsung — timpa jika Anda punya angka yang lebih akurat; angka yang diedit berlaku sebagai override tahunan tetap di semua tahun.")} />
        </div>
      </Card>

      <div className="vs-grid">
        <MaintenanceSourceCard en="Vehicle A" id="Kendaraan A" veh={vehA} annualKm={window.resolveAnnualKm(s)}
          overrideKey="maintOverrideA" overrideBasisKey="maintOverrideBasisA" overrideCycleKey="maintOverrideCycleA"
          tyreTier={s.tyreTierA} tyreTierPriceOverrides={s.tyreTierPriceOverrides} groupOverrides={s.maintGroupOverrides?.A}
          selectedYear={selectedYear} onSelectYear={setSelectedYear}
          s={s} set={set} lang={lang} />
        <div className="vs-divider"><span>VS</span></div>
        <MaintenanceSourceCard en="Vehicle B" id="Kendaraan B" veh={vehB} annualKm={window.resolveAnnualKm(s)}
          overrideKey="maintOverrideB" overrideBasisKey="maintOverrideBasisB" overrideCycleKey="maintOverrideCycleB"
          tyreTier={s.tyreTierB} tyreTierPriceOverrides={s.tyreTierPriceOverrides} groupOverrides={s.maintGroupOverrides?.B}
          selectedYear={selectedYear} onSelectYear={setSelectedYear}
          s={s} set={set} lang={lang} />
      </div>

      <MaintenanceBreakdownTable veh={vehA} annualKm={window.resolveAnnualKm(s)} lang={lang} s={s} overrideKey="maintOverrideA" selectedYear={selectedYear}
        tyreTier={s.tyreTierA} tyreTierPriceOverrides={s.tyreTierPriceOverrides}
        onTierChange={v => set("tyreTierA", v)} onPriceChange={setTierPrice}
        groupOverrides={s.maintGroupOverrides?.A} onGroupOverrideChange={(g, v) => setGroupOverride("A", g, v)} />
      <MaintenanceBreakdownTable veh={vehB} annualKm={window.resolveAnnualKm(s)} lang={lang} s={s} overrideKey="maintOverrideB" selectedYear={selectedYear}
        tyreTier={s.tyreTierB} tyreTierPriceOverrides={s.tyreTierPriceOverrides}
        onTierChange={v => set("tyreTierB", v)} onPriceChange={setTierPrice}
        groupOverrides={s.maintGroupOverrides?.B} onGroupOverrideChange={(g, v) => setGroupOverride("B", g, v)} />
    </>
  );
}

const SCREEN2_TABS = [
  { icon: "🚛", en: "Vehicle Selection", id: "Pemilihan Kendaraan" },
  { icon: "🔧", en: "Maintenance Breakdown", id: "Rincian Perawatan" },
  { icon: "📐", en: "Vehicle Specifications", id: "Spesifikasi Kendaraan" },
  { icon: "🧮", en: "Audit", id: "Audit" },
];

// ---------- Screen 2 Tab 3: Audit — vehicle CAPEX/maintenance financial rollup ----------
function Screen2AuditPanel({ s, lang }) {
  const vA = window.findVeh(s.vehA), vB = window.findVeh(s.vehB);
  if (!vA || !vB) {
    return (
      <WarnHint label={tr(lang, "Select Vehicles", "Pilih Kendaraan")}
        note={tr(lang, "Select both vehicles above to see the financial audit.", "Pilih kedua kendaraan di atas untuk melihat audit keuangan.")} />
    );
  }
  const R = window.computeTCO(s);
  const priceA = s.priceA ?? vA.price, priceB = s.priceB ?? vB.price;
  // Same priority-mode logic as the Maintenance tab's per-vehicle badge (§2.4).
  const modeA = maintActiveModeFor(s, "maintOverrideA"), modeB = maintActiveModeFor(s, "maintOverrideB");
  return (
    <div>
      <div className="audit-caption">
        {tr(lang, "Audit", "Audit")}
        <InfoHint note={tr(lang,
          "Financial audit for this section — fleet CAPEX and lifetime maintenance cost as they actually feed the TCO calculation, plus which maintenance-cost mode is active for each vehicle and any delta versus catalogue defaults.",
          "Audit keuangan untuk bagian ini — CAPEX armada dan biaya perawatan seumur hidup sebagaimana benar-benar masuk ke perhitungan TCO, beserta mode biaya perawatan mana yang aktif untuk setiap kendaraan dan selisihnya terhadap default katalog.")} />
      </div>
      <table className="sbs-table" style={{ marginTop: 14 }}>
        <thead>
          <tr><th>{tr(lang, "Item", "Item")}</th><th>{vA.name}</th><th>{vB.name}</th></tr>
        </thead>
        <tbody>
          <tr><td className="lbl">{tr(lang, "Unit Price (active)", "Harga Unit (aktif)")}</td><td className="num">{fmt.rp(priceA)}</td><td className="num">{fmt.rp(priceB)}</td></tr>
          <tr><td className="lbl">{tr(lang, "Catalogue Default Price", "Harga Default Katalog")}</td><td className="num">{fmt.rp(vA.price)}</td><td className="num">{fmt.rp(vB.price)}</td></tr>
          <tr><td className="lbl">{tr(lang, "Delta vs. Catalogue", "Selisih vs. Katalog")}</td>
            <td className="num">{priceA !== vA.price ? fmt.rp(priceA - vA.price) : "—"}</td>
            <td className="num">{priceB !== vB.price ? fmt.rp(priceB - vB.price) : "—"}</td></tr>
          <tr><td className="lbl">{tr(lang, "Fleet CAPEX", "CAPEX Armada")}</td><td className="num">{fmt.rp(R.rows[0].a)}</td><td className="num">{fmt.rp(R.rows[0].b)}</td></tr>
          <tr><td className="lbl">{tr(lang, "Maintenance Cost Mode", "Mode Biaya Perawatan")}</td><td>{tr(lang, modeA.en, modeA.id)}</td><td>{tr(lang, modeB.en, modeB.id)}</td></tr>
          <tr className="total"><td className="lbl">{tr(lang, "Lifetime Maintenance", "Perawatan Seumur Hidup")}</td><td className="num">{fmt.rp(R.rows[4].a)}</td><td className="num">{fmt.rp(R.rows[4].b)}</td></tr>
        </tbody>
      </table>
    </div>
  );
}

// Swap every "...A" / "...B" paired state key, so the A/B comparison
// flips sides — including maintenance overrides, tyre tier, energy
// consumption override, and breakdown details. Keep this list in sync
// with every A/B-suffixed key in DEFAULT_STATE (data.jsx) — a missing
// pair here silently corrupts the swapped side (e.g. energyOverrideA/B
// previously missing meant a vehicle moving slots kept the OTHER
// vehicle's energy-override units, producing a wildly wrong result).
const VEHICLE_AB_PAIRS = [
  ["vehA", "vehB"],
  ["priceA", "priceB"],
  ["maintOverrideA", "maintOverrideB"],
  ["maintOverrideBasisA", "maintOverrideBasisB"],
  ["maintOverrideCycleA", "maintOverrideCycleB"],
  ["tyreTierA", "tyreTierB"],
  ["energyOverrideA", "energyOverrideB"],
  ["payloadBuildA", "payloadBuildB"],
];

// yearlyOverrides keys are slot-based ("category.A.year" / "category.B.year",
// see data.jsx vehicleCalc's vehKey param) rather than tied to the vehicle's
// identity, so a plain field swap above doesn't follow them. Remap the
// embedded A/B token on every key so a per-year override stays attached to
// the vehicle it was entered for, not the slot.
function swapYearlyOverrides(yearlyOverrides) {
  if (!yearlyOverrides) return yearlyOverrides;
  const next = {};
  for (const [key, val] of Object.entries(yearlyOverrides)) {
    const parts = key.split(".");
    if (parts.length === 3 && (parts[1] === "A" || parts[1] === "B")) {
      parts[1] = parts[1] === "A" ? "B" : "A";
    }
    next[parts.join(".")] = val;
  }
  return next;
}

function swapVehiclesAB(s, set) {
  VEHICLE_AB_PAIRS.forEach(([ka, kb]) => {
    set(ka, s[kb]);
    set(kb, s[ka]);
  });
  set("yearlyOverrides", swapYearlyOverrides(s.yearlyOverrides));
}

function Screen2({ s, set }) {
  const { lang } = useLang();
  const activeTab = s.screen2_activeTab || 0;

  return (
    <>
      <Card>
        <div className="infra-tab-bar" style={{ alignItems: "center" }}>
          {SCREEN2_TABS.map((t, i) => (
            <div key={i} className={"infra-tab" + (activeTab === i ? " active" : "")}
              onClick={() => set("screen2_activeTab", i)}>
              <span>{t.icon}</span> {tr(lang, t.en, t.id)}
            </div>
          ))}
          <div style={{ marginLeft: "auto", display: "flex", gap: 8, alignSelf: "center" }}>
            <button className="btn btn-ghost" onClick={() => swapVehiclesAB(s, set)}>
              ⇄ <Tr en="Flip A ↔ B" id="Tukar A ↔ B" />
            </button>
            <ResetScreenButton s={s} set={set} screenKey="screen2" />
          </div>
        </div>
      </Card>

      {activeTab === 0 && (
        <div className="vs-grid">
          <VehicleCard
            en="Vehicle A" id="Kendaraan A"
            vehKey="vehA" priceKey="priceA"
            overrideKey="maintOverrideA" overrideBasisKey="maintOverrideBasisA" overrideCycleKey="maintOverrideCycleA"
            energyOverrideKey="energyOverrideA"
            s={s} set={set}
          />
          <div className="vs-divider"><span>VS</span></div>
          <VehicleCard
            en="Vehicle B" id="Kendaraan B"
            vehKey="vehB" priceKey="priceB"
            overrideKey="maintOverrideB" overrideBasisKey="maintOverrideBasisB" overrideCycleKey="maintOverrideCycleB"
            energyOverrideKey="energyOverrideB"
            s={s} set={set}
          />
        </div>
      )}
      {activeTab === 1 && <MaintenanceBreakdownPanel s={s} set={set} lang={lang} />}
      {activeTab === 2 && <VehicleSpecificationsPanel s={s} set={set} />}
      {activeTab === 3 && <Screen2AuditPanel s={s} lang={lang} />}
    </>
  );
}

// ---------- Screen 3: Operation (was Screen 2 — labor card removed) ----------
const SCREEN3_TABS = [
  { icon: "🚛", en: "Inputs", id: "Input" },
  { icon: "🧮", en: "Audit", id: "Audit" },
];

function Screen3Inputs({ s, set, lang }) {
  const tp = s.trackProfile || {};
  const setTp = (patch) => set("trackProfile", { ...tp, ...patch });
  const computed = tp.enabled ? window.computeTrackProfile(tp) : null;
  // Ritase-Cycle Engine (v1.8, §10 CALCULATION_ENGINE.md) -- full
  // charge-cycle math, still null unless an EV vehicle is selected AND its
  // battery/energy spec resolves a usable range. Kept around here only for
  // the "range too short" edge-case warning below (charge-cycle-specific).
  const rc = window.computeRitaseCycle(s);
  // v1.9.4: Annual Mileage itself no longer depends on rc directly -- it's
  // driven by resolveAutoAnnualMileage (data.jsx), which falls back to a
  // time-window-constrained ritase count (no battery/charging requirement)
  // whenever the full charge-cycle engine can't resolve, so Annual Mileage
  // is auto-computed for ICE-only and no-resolvable-range comparisons too.
  // Rija: "I WANT THE ANNUAL MILEAGE TO BE AUTOMATED ... DO NOT MAKE ANNUAL
  // MILEAGE USER EDITABLE" -- the only remaining raw-editable case is
  // Ritase Distance itself not being set yet.
  const autoMileage = window.resolveAutoAnnualMileage(s);
  // v1.9.13: rc.Z_RC === 0 (EV usable range shorter than Ritase Distance)
  // now falls through to the time-window basis in resolveAutoAnnualMileage
  // (data.jsx) instead of zeroing Annual Mileage -- so this must check
  // rc.Z_RC directly, not autoMileage.basis, or the warning would silently
  // stop firing for the exact case it exists to explain.
  const evRangeTooShort = !!(rc && rc.Z_RC === 0);
  const timeWindowTooShort = !!(!evRangeTooShort && autoMileage && autoMileage.basis === "time-window" && autoMileage.ritaseCountPerDay === 0);
  const zeroRangeWarn = evRangeTooShort || timeWindowTooShort;
  return (
    <>
    <Card title="Fleet & Ritase" idSub="Armada & Ritase">
      <div className="grid-3">
        <Field en="Annual Mileage" id="Jarak Tempuh Tahunan"
          helpEn={autoMileage
            ? (autoMileage.basis === "charge-cycle"
              ? "Computed: Ritase Distance x daily ritase count x operating days/year, limited by EV battery range per charge (Ritase-Cycle Engine, see Audit tab). Edit Ritase Distance below to change this."
              : "Computed: Ritase Distance x daily ritase count x operating days/year, limited by how many round trips fit the operating window (no EV/battery constraint applies here). Edit Ritase Distance below to change this.")
            : "Raw input -- becomes a computed, read-only value once Ritase Distance below is set."}
          help={autoMileage
            ? (autoMileage.basis === "charge-cycle"
              ? "Dihitung: Jarak Ritase x jumlah ritase harian x hari operasional/tahun, dibatasi jangkauan baterai EV per pengisian (Mesin Siklus Ritase, lihat tab Audit). Ubah Jarak Ritase di bawah untuk mengubah ini."
              : "Dihitung: Jarak Ritase x jumlah ritase harian x hari operasional/tahun, dibatasi berapa banyak perjalanan pulang-pergi yang muat dalam jendela operasi (tidak ada batasan EV/baterai di sini). Ubah Jarak Ritase di bawah untuk mengubah ini.")
            : "Input mentah -- menjadi nilai terhitung (read-only) setelah Jarak Ritase di bawah diisi."}>
          {autoMileage ? (
            <div className="affix readonly">
              <span className="fix pre">📏</span>
              <input value={`${fmt.num(Math.round(autoMileage.annualKmDerived))} km/thn (computed)`} readOnly />
            </div>
          ) : (
            <AffixInput value={fmt.num(s.annualKm)} suffix="km/thn"
              onChange={v => set("annualKm", Number(v.replace(/\D/g, "")) || 0)} />
          )}
          {!autoMileage && (
            <div style={{ marginTop: 6 }}>
              <WarnHint label={tr(lang, "Not Yet Computed", "Belum Dihitung")}
                note={tr(lang, "Ritase Distance is not set yet -- enter it below.", "Jarak Ritase belum diisi -- isi di bawah.")} />
            </div>
          )}
          {zeroRangeWarn && (
            <div style={{ marginTop: 6 }}>
              <WarnHint label={tr(lang, "Range/Window Too Short", "Jangkauan/Jendela Terlalu Pendek")}
                note={evRangeTooShort
                  ? tr(lang,
                    "The selected EV's usable range is shorter than the Ritase Distance -- it cannot complete one round trip per charge under this platform's charging model. Annual Mileage below now falls back to a time-window-based estimate (no charging-range limit) instead of going to 0, but treat this route as not achievable without a mid-trip charge.",
                    "Jangkauan terpakai EV terpilih lebih pendek dari Jarak Ritase -- tidak bisa menyelesaikan satu perjalanan pulang-pergi per pengisian dengan model pengisian daya platform ini. Jarak Tahunan di bawah kini beralih ke estimasi berbasis jendela waktu (tanpa batas jangkauan pengisian) alih-alih menjadi 0, tetapi anggap rute ini tidak dapat dicapai tanpa pengisian di tengah perjalanan.")
                  : tr(lang,
                    "One round trip (Ritase Time) takes longer than the available operating window -- it cannot complete even once a day, so ritase count and annual mileage compute to 0.",
                    "Satu perjalanan pulang-pergi (Waktu Ritase) lebih lama dari jendela operasi yang tersedia -- tidak bisa selesai bahkan sekali sehari, sehingga jumlah ritase dan jarak tahunan dihitung 0.")} />
            </div>
          )}
        </Field>
        <div />
        <div />
      </div>

      <div className="grid-2" style={{ marginTop: 18 }}>
        <SliderField en="Fleet Size" id="Jumlah Unit" min={1} max={80}
          value={s.fleetSize} onChange={v => set("fleetSize", v)} unit="unit" />
        <SliderField en="Analysis Horizon" id="Horizon Analisis" min={1} max={20}
          value={s.horizon} onChange={v => set("horizon", v)} unit="tahun" />
      </div>

      <div className="grid-3" style={{ marginTop: 18 }}>
        <Field en="Ritase Distance" id="Jarak Ritase"
          helpEn="One round-trip depot -> operation site -> depot distance. Drives ritase count, annual mileage, and the Charging Strategy cycle math (Infrastructure tab)."
          help="Jarak satu perjalanan pulang-pergi depot -> lokasi operasi -> depot. Menentukan jumlah ritase, jarak tahunan, dan perhitungan siklus Strategi Pengisian Daya (tab Infrastruktur).">
          <AffixInput value={fmt.num(s.ritaseDistanceKm)} suffix="km" disabled={tp.enabled}
            onChange={v => set("ritaseDistanceKm", Number(v.replace(/[^\d.]/g, "")) || 0)} />
        </Field>
        <Field en="Terrain" id="Medan"
          helpEn="Terrain affects the maintenance cost presets shown in Vehicle Selection (Screen 2), and the auto-computed Ritase Time."
          help="Medan mempengaruhi preset biaya perawatan yang ditampilkan di Pemilihan Kendaraan (Layar 2), dan Waktu Ritase yang dihitung otomatis.">
          {tp.enabled ? (
            <div className="affix readonly">
              <span className="fix pre">⛰</span>
              <input value={computed ? `${computed.contour} (computed)` : "— set track profile below —"} readOnly />
            </div>
          ) : (
            <Select value={s.terrainManual} onChange={v => set("terrainManual", v)}
              options={TERRAINS.map(t => ({ value: t, label: t }))} />
          )}
        </Field>
        <Field en="Ritase Time" id="Waktu Ritase"
          helpEn="Auto-computed from Ritase Distance and a vehicle-segment/terrain average-speed assumption. Type a value to override the auto-computed one."
          help="Dihitung otomatis dari Jarak Ritase dan asumsi kecepatan rata-rata segmen kendaraan/medan. Ketik nilai untuk mengganti hasil otomatis.">
          <AffixInput value={fmt.num(Math.round(s.ritaseTimeOverride ?? (autoMileage ? autoMileage.RT : 0)))} suffix="min"
            onChange={v => { const n = Number(v.replace(/\D/g, "")); set("ritaseTimeOverride", n > 0 ? n : null); }} />
          <ValueFlag defaultValue={autoMileage ? Math.round(autoMileage.RT) : null} currentValue={s.ritaseTimeOverride}
            onReset={() => set("ritaseTimeOverride", null)} />
        </Field>
      </div>
    </Card>

    <Card title="Track Profile (generalized route input)" idSub="Profil Rute (input umum)">
      <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", marginBottom: 14 }}>
        <input type="checkbox" checked={!!tp.enabled} onChange={e => setTp({ enabled: e.target.checked })} />
        <Tr en="Use a computed track profile instead of manual terrain/ritase-distance entry (e.g. from a GPX route survey) -- its Distance field doubles as Ritase Distance when enabled"
            id="Gunakan profil rute terhitung sebagai pengganti pemilihan medan/jarak ritase manual (misal dari survei rute GPX) -- kolom Jarak-nya juga berfungsi sebagai Jarak Ritase saat diaktifkan" />
      </label>

      <RouteImportPanel onApply={(patch) => setTp(patch)} />
      <RouteLiveLookup onApply={(patch) => setTp(patch)} />

      {tp.enabled && (
        <>
          <div className="grid-3">
            <Field en="Distance" id="Jarak">
              <AffixInput value={fmt.num(tp.distanceKm || 0)} suffix="km"
                onChange={v => setTp({ distanceKm: Number(v.replace(/[^\d.]/g, "")) || null })} />
            </Field>
            <Field en="Total Elevation Gain" id="Total Kenaikan Elevasi">
              <AffixInput value={fmt.num(tp.elevGainM || 0)} suffix="m"
                onChange={v => setTp({ elevGainM: Number(v.replace(/[^\d.]/g, "")) || null })} />
            </Field>
            <Field en="Total Elevation Loss" id="Total Penurunan Elevasi">
              <AffixInput value={fmt.num(tp.elevLossM || 0)} suffix="m"
                onChange={v => setTp({ elevLossM: Number(v.replace(/[^\d.]/g, "")) || null })} />
            </Field>
          </div>
          <div className="grid-2" style={{ marginTop: 14 }}>
            <Field en="Net Elevation Between Endpoints" id="Selisih Elevasi Antar Titik Akhir">
              <AffixInput value={String(tp.netElevDeltaM ?? ((tp.elevGainM || 0) - (tp.elevLossM || 0)))} suffix="m"
                onChange={v => setTp({ netElevDeltaM: Number(v.replace(/[^\d.-]/g, "")) || 0 })} />
            </Field>
            <Field en="EV Regen-Credit Rate" id="Tingkat Kredit Regen EV">
              <AffixInput value={fmt.num(Math.round((tp.oscillationCreditRate ?? 0.4) * 100))} suffix="%"
                onChange={v => setTp({ oscillationCreditRate: (Number(v.replace(/\D/g, "")) || 0) / 100 })} />
              <ValueFlag defaultValue={40} currentValue={Math.round((tp.oscillationCreditRate ?? 0.4) * 100)}
                onReset={() => setTp({ oscillationCreditRate: 0.4 })} />
            </Field>
          </div>
          {computed && (
            <div className="spec-grid" style={{ marginTop: 16 }}>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "Contour (computed)", "Kontur (terhitung)")}</div>
                <div className="sv">{computed.contour} <span style={{ color: "var(--text-muted)", fontSize: 11 }}>({computed.gainPerKm.toFixed(1)} m/km)</span></div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "Variance (computed)", "Variasi (terhitung)")}</div>
                <div className="sv">{computed.varianceLabel} <span style={{ color: "var(--text-muted)", fontSize: 11 }}>(idx {computed.linearityIndex.toFixed(2)})</span></div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "ICE Terrain Multiplier", "Multiplier Medan ICE")}</div>
                <div className="sv">{computed.iceMultiplier.toFixed(3)}×</div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "EV Terrain Multiplier (regen credit applied)", "Multiplier Medan EV (kredit regen diterapkan)")}</div>
                <div className="sv">{computed.evMultiplier.toFixed(3)}× <span style={{ color: "var(--text-muted)", fontSize: 11 }}>(−{computed.regenCredit.toFixed(3)})</span></div>
              </div>
            </div>
          )}
          <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 12, display: "flex", alignItems: "center" }}>
            {tr(lang, "Terrain formula", "Formula medan")}
            <InfoHint note={tr(lang,
              "Contour is computed from gain/km (Flat <10, Rolling 10-18, Hilly ≥30, Mixed in between with blended variance). EV multiplier gets a regen-braking credit on choppy routes: regenCredit = (1−linearityIndex) × creditRate × (terrainMultiplier−1). Full formula audit available in Results → Calculation Steps.",
              "Kontur dihitung dari kenaikan/km (Datar <10, Berbukit 10-18, Curam ≥30, Campuran di antaranya dengan variasi campuran). Multiplier EV mendapat kredit pengereman regen pada rute bergelombang: regenCredit = (1−indeksLinearitas) × tingkatKredit × (multiplierMedan−1). Audit formula lengkap tersedia di Hasil → Langkah Kalkulasi.")} />
          </div>
        </>
      )}
    </Card>
    </>
  );
}

// ---------- Screen 3 Tab 2: Audit — route/terrain/mileage multiplier rollup ----------
function Screen3AuditPanel({ s, lang }) {
  const tp = s.trackProfile || {};
  const computed = tp.enabled ? window.computeTrackProfile(tp) : null;
  const terrain = computed ? computed.contour : s.terrainManual;
  const terrainMultiplier = computed ? computed.terrainMultiplier : (window.TERRAIN_MULTIPLIER[terrain] ?? 1.0);
  const iceMultiplier = computed ? computed.iceMultiplier : terrainMultiplier;
  const evMultiplier = computed ? computed.evMultiplier : terrainMultiplier;
  const rc = window.computeRitaseCycle(s);
  const autoMileage = window.resolveAutoAnnualMileage(s);
  const annualKmResolved = window.resolveAnnualKm(s);
  // mileScale formula mirrors maintCostForYear (CALCULATION_ENGINE.md §5):
  // PM schedule values are baselined at 30,000 km/yr, scaled linearly.
  const mileScale = (annualKmResolved || 50000) / 30000;
  return (
    <div>
      <div className="audit-caption">
        {tr(lang, "Audit", "Audit")}
        <InfoHint note={tr(lang,
          "Financial audit for this section — the terrain and mileage multipliers actually applied to energy and maintenance costs downstream, not just the raw inputs above.",
          "Audit keuangan untuk bagian ini — multiplier medan dan jarak tempuh yang benar-benar diterapkan pada biaya energi dan perawatan selanjutnya, bukan hanya input mentah di atas.")} />
      </div>
      <div className="spec-grid" style={{ marginTop: 14 }}>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Terrain / Contour", "Medan / Kontur")}</div>
          <div className="sv">{terrain || "—"} {computed && <span style={{ fontSize: 11, color: "var(--text-muted)" }}>({computed.varianceLabel})</span>}</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Terrain Multiplier (maintenance, both powertrains)", "Multiplier Medan (perawatan, kedua jenis penggerak)")}</div>
          <div className="sv">{terrainMultiplier.toFixed(3)}×</div>
        </div>
        {computed && (
          <>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "ICE Energy Multiplier", "Multiplier Energi ICE")}</div>
              <div className="sv">{iceMultiplier.toFixed(3)}×</div>
            </div>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "EV Energy Multiplier (regen credit −" + computed.regenCredit.toFixed(3) + ")", "Multiplier Energi EV (kredit regen −" + computed.regenCredit.toFixed(3) + ")")}</div>
              <div className="sv">{evMultiplier.toFixed(3)}×</div>
            </div>
          </>
        )}
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Annual Mileage", "Jarak Tempuh Tahunan")}</div>
          <div className="sv">{fmt.num(Math.round(annualKmResolved))} km/thn {autoMileage && <span style={{ fontSize: 11, color: "var(--text-muted)" }}>({tr(lang, "computed", "terhitung")})</span>}</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Mileage Scale (vs. 30,000 km/yr PM baseline)", "Skala Jarak Tempuh (vs. baseline PM 30.000 km/thn)")}</div>
          <div className="sv">{mileScale.toFixed(2)}×</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Fleet Size", "Jumlah Unit")}</div>
          <div className="sv">{s.fleetSize} {tr(lang, "units", "unit")}</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Analysis Horizon", "Horizon Analisis")}</div>
          <div className="sv">{s.horizon} {tr(lang, "years", "tahun")}</div>
        </div>
      </div>
      <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 14, display: "flex", alignItems: "center" }}>
        {tr(lang, "Scaling note", "Catatan skala")}
        <InfoHint note={tr(lang,
          "Energy cost scales by terrainMultiplier directly; the standard PM maintenance schedule scales by both terrainMultiplier and mileScale (CALCULATION_ENGINE.md §5). The parts-based maintenance estimate (Screen 2 → Maintenance Breakdown), if enabled, scales by annual km directly per part rather than this flat mileScale.",
          "Biaya energi diskalakan langsung oleh terrainMultiplier; jadwal perawatan PM standar diskalakan oleh terrainMultiplier dan mileScale (CALCULATION_ENGINE.md §5). Estimasi perawatan berbasis komponen (Layar 2 → Rincian Perawatan), jika diaktifkan, diskalakan berdasarkan km tahunan langsung per komponen, bukan mileScale datar ini.")} />
      </div>

      {rc && (
        <>
          <div className="audit-caption" style={{ marginTop: 20 }}>
            {tr(lang, "Ritase-Cycle Engine", "Mesin Siklus Ritase")}
            <InfoHint note={tr(lang,
              "How Annual Mileage above was derived: ritase distance and vehicle range set the ritase count per charge; the Infrastructure tab's proposed cycle/group counts set how many cycles run per day. See CALCULATION_ENGINE.md §10.",
              "Cara Jarak Tempuh Tahunan di atas diturunkan: jarak ritase dan jangkauan kendaraan menentukan jumlah ritase per pengisian; jumlah siklus/grup yang diusulkan di tab Infrastruktur menentukan berapa siklus berjalan per hari. Lihat CALCULATION_ENGINE.md §10.")} />
          </div>
          <div className="spec-grid" style={{ marginTop: 14 }}>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "Vehicle Range (VR)", "Jangkauan Kendaraan (VR)")}</div>
              <div className="sv">{fmt.num(Math.round(rc.VR))} km</div>
            </div>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "Ritase Count per Charge (Z_RC)", "Jumlah Ritase per Pengisian (Z_RC)")}</div>
              <div className="sv">{rc.Z_RC}</div>
            </div>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "Daily Ritase / Vehicle (Z_DR)", "Ritase Harian / Kendaraan (Z_DR)")}</div>
              <div className="sv">{rc.Z_DR}</div>
            </div>
            <div className="spec-cell">
              <div className="sk">{tr(lang, "Daily Payload / Vehicle (TP_D)", "Muatan Harian / Kendaraan (TP_D)")}</div>
              <div className="sv">{fmt.num(Math.round(rc.TP_D))} kg</div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function Screen3({ s, set }) {
  const { lang } = useLang();
  const activeTab = s.screen3_activeTab || 0;
  return (
    <>
      <Card>
        <div className="infra-tab-bar">
          {SCREEN3_TABS.map((t, i) => (
            <div key={i} className={"infra-tab" + (activeTab === i ? " active" : "")}
              onClick={() => set("screen3_activeTab", i)}>
              <span>{t.icon}</span> {tr(lang, t.en, t.id)}
            </div>
          ))}
          <div style={{ marginLeft: "auto", alignSelf: "center" }}>
            <ResetScreenButton s={s} set={set} screenKey="screen3" />
          </div>
        </div>
      </Card>
      {activeTab === 0 && <Screen3Inputs s={s} set={set} lang={lang} />}
      {activeTab === 1 && <Screen3AuditPanel s={s} lang={lang} />}
    </>
  );
}

// ---------- Screen 4: Infrastructure (v1.3 — preset + 5-tab rebuild) ----------

// v1.5: CAPEX Breakdown moved last — it's this section's financial audit
// page (one coherent total covering whichever source — Sizing Engine or
// Depot Design — is actually live), so it belongs after every input tab
// that feeds it, matching the same "audit tab last" pattern applied to
// Vehicle Selection, Operation, and Financials.
// v1.21 — Fleet Profile + Charging Strategy merged into one "Fleet & Charging"
// tab (5 tabs total now, was 6). Each tab's EXPERT_TAB_KEY below maps it to
// its own screen4_expertMode_tabN flag — Depot Design has no entry (its
// ExpertToggle stays excluded, as before the merge).
const INFRA_TABS = [
  { key: "fleet",    en: "Fleet & Charging",  idLbl: "Armada & Pengisian",  icon: "🚛", expertTabKey: "screen4_expertMode_tab1" },
  { key: "growth",   en: "Growth & Assets",   idLbl: "Pertumbuhan & Aset",  icon: "📊", expertTabKey: "screen4_expertMode_tab2" },
  { key: "sizing",   en: "Sizing Engine",     idLbl: "Mesin Perhitungan",   icon: "🔧", expertTabKey: "screen4_expertMode_tab3" },
  { key: "depot",    en: "Depot Design",      idLbl: "Desain Depot",        icon: "🏗️", expertTabKey: null },
  { key: "capex",    en: "CAPEX Breakdown",   idLbl: "Rincian CAPEX",       icon: "💰", expertTabKey: "screen4_expertMode_tab4" },
];

// Depot Floor Plan tool, embedded live from depot_floorplan/ (served alongside this
// platform at localhost:8000/depot_floorplan/). The same files are also independently
// servable standalone at localhost:8001 (see depot_floorplan/start_depot.bat) for
// modularity and audit — this tab does not fork or copy any depot code, it postMessage-
// syncs with it. See DEPOT_INTEGRATION_HANDOVER.md and project memory depot_tco_integration.md.

// TCO's 172 vehicles only carry `segment` + `gvw` (no physical L/W), so the
// match to depot's 4 VSPEC templates is a heuristic, not exact — shown to
// the user as an editable suggestion in depot's own vehicle-type grid
// (locked here only insofar as TCO supplies the starting pick; the grid
// itself stays visible and the user can still click a different card).
function mapVehicleToDepotVt(veh) {
  if (!veh) return null;
  const seg = veh.segment;
  if (seg === "BUS") return (veh.gvw || 0) < 12000 ? "bus12" : "bus18";
  if (seg === "LDT" || seg === "MDT") return "truck12";
  if (seg === "HDT" || seg === "TH") return "truck18";
  return "truck12"; // VAN / Pickup / Double Cabin — no depot template exists yet, closest fallback
}
const DEPOT_VT_LABELS = {
  bus12: { en: "12m City Bus", id: "Bus Kota 12m" },
  bus18: { en: "18m Articulated Bus", id: "Bus Tempel 18m" },
  truck12: { en: "Rigid Truck 12m", id: "Truk Rigid 12m" },
  truck18: { en: "Semi-Trailer 18m", id: "Truk Trailer 18m" },
};

// Bridges TCO's ecosystem/project-type cost scaling (MASTER_MULTIPLIER) into
// depot's own flat, ecosystem-blind BOM_REF (depot_floorplan/js/bom.js),
// which is sourced closest to a logistics+greenfield baseline and has no
// per-ecosystem variants of its own. Computed relative to that baseline
// (which IS exactly 1.0 by design in infra_profiles.js, but divided through
// explicitly rather than assumed, in case those defaults ever change) so a
// logistics/greenfield project sends a pure no-op (every factor = 1).
function computeDepotCostAdjustment(ecosystemId) {
  const MM = window.INFRA_PROFILES.MASTER_MULTIPLIER;
  const baseCivil = MM("logistics", "civilWorksMultiplier");
  const baseUtility = MM("logistics", "utilityUpgradeMultiplier");
  const baseSoftware = MM("logistics", "softwareMultiplier");
  const civil = MM(ecosystemId, "civilWorksMultiplier");
  const utility = MM(ecosystemId, "utilityUpgradeMultiplier");
  const software = MM(ecosystemId, "softwareMultiplier");
  return {
    // depot's "site" category = TCO's civil works (C) + utility upgrade (D) combined
    site: (civil / baseCivil) * (utility / baseUtility),
    // depot has no direct TCO-category analog for "building" — civil works is closest
    building: civil / baseCivil,
    software: software / baseSoftware,
    salvageCreditPct: 0, // v1.7.7: was replacement-project-type-only; Greenfield-only now
  };
}

function Tab6DepotDesign({ s, set, lang }) {
  const iframeRef = useRef(null);
  const [iframeReady, setIframeReady] = useState(false);

  const veh = window.getEvVehicle(s);
  const ecosystemId = s.ecosystemId || "others";
  const effectiveEcosystemId = s.costModelFlat ? "others" : ecosystemId;
  const sizing = window.computeSizing(s);
  const depotVt = mapVehicleToDepotVt(veh);
  const evInfraType = veh ? (window.EV_INFRA[veh.id] || "charge") : null;
  const growthMarginDefault = window.INFRA_PROFILES.MASTER_MULTIPLIER(effectiveEcosystemId, "growthMargin");
  const costAdjustment = computeDepotCostAdjustment(effectiveEcosystemId);

  // Depot's "Diversity factor" (Expert Mode, feeds its trafoKVA calc) is the
  // same concept as TCO's own ecosystem diversityFactor -- previously a flat
  // depot-local default (0.85) regardless of which ecosystem TCO had
  // selected. (Bay-count reserve is a flat +1 rule on the depot side now,
  // not percentage-based, so no TCO sync needed for that.)
  const diversityFactorDefault = window.INFRA_PROFILES.MASTER_MULTIPLIER(effectiveEcosystemId, "diversityFactor");

  // Resolved simultaneous-nozzle count for whichever slot (A/B) is the EV —
  // same CONTRACT formula used in data.jsx (override ?? catalogue default).
  const nozzlesPerVehicle = veh
    ? (s.vehA === veh.id ? s.nozzlesPerVehicleOverrideA
        : (s.vehB === veh.id ? s.nozzlesPerVehicleOverrideB : null)) ?? (window.EV_NOZZLE_COUNT[veh.id] ?? 1)
    : 1;

  // Resolved "vehicles per charging bay" -- same single value TCO's own
  // Sizing Engine uses for its throughput-charger-count cross-check
  // (sizing.chargerRatio: shift-count-derived when Fleet Plan is shift-based,
  // else the per-segment default, else any user/Expert override). Synced to
  // the depot tool so its own bay-count formula shares this one source of
  // truth instead of sizing blind to shift structure.
  const vehiclesPerChargingBay = sizing ? sizing.chargerRatio : null;

  // Re-send the sync payload whenever the inputs TCO owns actually change —
  // not on every keystroke elsewhere on the platform, so this stays cheap.
  const syncKey = JSON.stringify([s.fleetSize, depotVt, sizing && sizing.chargerRatingKw, evInfraType, ecosystemId, s.costModelFlat, nozzlesPerVehicle, vehiclesPerChargingBay, sizing && sizing.chargerCount]);

  useEffect(() => {
    if (!iframeReady || !veh || !depotVt) return;
    const win = iframeRef.current && iframeRef.current.contentWindow;
    if (!win) return;
    win.postMessage({
      type: "tco:sync",
      payload: {
        fleet: s.fleetSize,
        vt: depotVt,
        vtLabel: DEPOT_VT_LABELS[depotVt] ? DEPOT_VT_LABELS[depotVt].en : depotVt,
        chgKW: sizing ? sizing.chargerRatingKw : null,
        divF: diversityFactorDefault,
        infraMode: evInfraType === "both" ? "both" : (evInfraType === "swap" ? "swap" : "charge"),
        growthMarginDefault,
        ecosystemId,
        costAdjustment,
        nozzlesPerVehicle,
        vehiclesPerChargingBay,
        // v1.7.8 -- TCO's own power/energy-derived charger count, used as a
        // FLOOR on depot's bay-count target (see applyTcoSync/neededBays,
        // compute.js) so default (non-Studio) auto-sizing genuinely can't
        // undersize relative to what TCO itself calculated it needs.
        tcoChargerCount: sizing ? sizing.chargerCount : null,
      },
    }, window.location.origin);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [iframeReady, syncKey]);

  useEffect(() => {
    function onMessage(e) {
      if (e.origin !== window.location.origin) return;
      if (!iframeRef.current || e.source !== iframeRef.current.contentWindow) return;
      if (!e.data || e.data.type !== "depot:result") return;
      set("depotBom", e.data.payload.bom);
      set("depotBomInclude", e.data.payload.bomInclude || null);
      set("depotMetrics", { ...e.data.payload.metrics, planSummary: e.data.payload.planSummary, layoutSvg: e.data.payload.layoutSvg || null });
    }
    window.addEventListener("message", onMessage);
    return () => window.removeEventListener("message", onMessage);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const matchedLabel = depotVt && DEPOT_VT_LABELS[depotVt] ? tr(lang, DEPOT_VT_LABELS[depotVt].en, DEPOT_VT_LABELS[depotVt].id) : null;

  return (
    <div>
      <div className="audit-caption" style={{ textTransform: "none" }}>
        {tr(lang, "Depot Design", "Desain Depot")}
        <InfoHint note={tr(lang,
          "This embeds the EV Depot Floor Plan tool, kept independently auditable at localhost:8001. Fleet size, vehicle template, charger rating, and charging type sync here automatically from your selections on Screens 2–4; site geometry, building, and layout plan are this tab's own local inputs. Whichever plan you mark “Select for TCO” inside the tool (Plan 1 by default) feeds its BOM/CAPEX/OPEX into this screen's CAPEX Breakdown tab and the Results screen.",
          "Tab ini menyematkan alat Desain Tata Letak Depot EV, yang tetap dapat diaudit secara independen di localhost:8001. Jumlah armada, jenis kendaraan, daya charger, dan tipe pengisian otomatis disinkronkan dari pilihan Anda di Layar 2–4; geometri lokasi, bangunan, dan pilihan rencana tata letak adalah input lokal khusus tab ini. Rencana mana pun yang Anda tandai “Select for TCO” di dalam alat ini (Rencana 1 secara default) akan mengalirkan BOM/CAPEX/OPEX-nya ke tab Rincian CAPEX layar ini dan ke Layar Hasil.")} />
      </div>
      <div>
        {veh && depotVt && (
          <div style={{ marginTop: 6, fontWeight: 600, display: "flex", alignItems: "center" }}>
            {tr(lang, "Auto-matched vehicle template: ", "Templat kendaraan otomatis: ")}{matchedLabel}
            <InfoHint note={tr(lang, "Editable inside the tool's Vehicle Type grid below.", "Dapat diubah pada kotak Jenis Kendaraan di alat di bawah.")} />
          </div>
        )}
        {!veh && (
          <div style={{ marginTop: 6, display: "flex", alignItems: "center" }}>
            {tr(lang, "No EV selected", "Belum ada EV dipilih")}
            <InfoHint note={tr(lang, "Select an EV in Vehicle Selection (Screen 2) to auto-sync fleet and charging inputs here.", "Pilih EV di Pemilihan Kendaraan (Layar 2) untuk menyinkronkan otomatis input armada dan pengisian di sini.")} />
          </div>
        )}
        {s.depotBom && (
          <div style={{ marginTop: 6, color: "var(--c-accent)", fontWeight: 600, display: "flex", alignItems: "center" }}>
            {tr(lang, "✓ Depot Design is live", "✓ Desain Depot aktif")}
            <InfoHint note={tr(lang, "Its CAPEX/OPEX now feeds the CAPEX Breakdown tab and Results screen.", "CAPEX/OPEX-nya sekarang menjadi sumber tab Rincian CAPEX dan Layar Hasil.")} />
          </div>
        )}
        {/* v1.7.8: the excluded-CAPEX and undersized-provisioning warnings
            that used to sit here are gone -- electrical-only CAPEX scope is
            now a permanent, always-true-by-design state (not a fluctuating
            condition worth flagging every time), and default (non-Studio)
            depot sizing now genuinely floors on TCO's own chargerCount (see
            ST.tcoChargerCount, depot_floorplan/js/compute.js), so a real
            shortfall outside Studio mode shouldn't occur anymore. */}
        {(Math.abs(costAdjustment.site - 1) > 0.01 || Math.abs(costAdjustment.building - 1) > 0.01 || Math.abs(costAdjustment.software - 1) > 0.01 || costAdjustment.salvageCreditPct > 0) && (
          <div style={{ marginTop: 6, fontSize: 12, color: "var(--text-muted)", display: "flex", alignItems: "center" }}>
            {tr(lang, "Ecosystem/project-type adjustment applied", "Penyesuaian ekosistem/tipe proyek diterapkan")}
            <InfoHint note={
              `${tr(lang, "Applied to Depot Design's BOM: ", "Diterapkan ke BOM Desain Depot: ")}` +
              `${tr(lang, "site/utility ×", "lokasi/utilitas ×")}${costAdjustment.site.toFixed(2)}, ${tr(lang, "building ×", "bangunan ×")}${costAdjustment.building.toFixed(2)}, ${tr(lang, "software ×", "perangkat lunak ×")}${costAdjustment.software.toFixed(2)}` +
              `${costAdjustment.salvageCreditPct > 0 ? `, ${tr(lang, "salvage credit ", "kredit sisa aset ")}${(costAdjustment.salvageCreditPct * 100).toFixed(0)}%` : ""}` +
              ` ${tr(lang, "(depot's own flat reference costs adjusted relative to a logistics/greenfield baseline — see project memory for the methodology).", "(biaya referensi datar milik depot disesuaikan relatif terhadap basis logistik/greenfield — lihat memori proyek untuk metodologinya).")}`
            } />
          </div>
        )}
      </div>
      <iframe
        ref={iframeRef}
        className="depot-embed-frame"
        src="depot_floorplan/index.html"
        title="EV Depot Floor Plan"
        onLoad={() => setIframeReady(true)}
      />
    </div>
  );
}

// screen4_valueOverrides helper — keyed "tabN.paramName"
function setOv(set, s, key, value) {
  const next = { ...(s.screen4_valueOverrides || {}) };
  if (value == null) delete next[key]; else next[key] = value;
  set("screen4_valueOverrides", next);
}

const CHARGING_TYPE_OPTIONS = [
  { id: "ac",     en: "AC",      idLbl: "AC",      sub: "≤ 22 kW",      icon: "🔌" },
  { id: "dc",     en: "DC",      idLbl: "DC",      sub: "40 – 120 kW",  icon: "⚡" },
  { id: "dcfast", en: "DC Fast", idLbl: "DC Cepat", sub: "150 – 360 kW", icon: "🚀" },
];
// ---------- Day Timeline visual (shifts/fixed-window vs charging window) ----------
// 24-hour horizontal bar: shift/operating blocks in one color, the resulting
// chargeable window in the accent color. Replaces the old plain-text-only
// callout as the primary visual; the original numeric summary is kept below
// it as supporting detail (per spec — text retained, not removed).
// v1.8 (2026-07-17): re-based on the Ritase-Cycle Engine (§10
// CALCULATION_ENGINE.md) -- draws Z_CC repeating cycles across the 24h
// view, each with Z_TG sequential scheduled-group (SS) slots + a trailing
// unscheduled (US) block, instead of the pre-v1.8 shift/fixed-window
// model. One integrated Gantt for every scenario (no more
// shift/fixed/opportunity branching).
function DayTimelineSVG({ ritaseCycle, lang }) {
  const W = 700, H = 92;
  const trackY = 30, trackH = 34;
  const hourW = W / 24;
  const rc = ritaseCycle;
  const ssHours = rc.SS / 60;
  const usHours = rc.US / 60;
  const groupPalette = ["#5B8DEF", "#7C6AE0", "#3FB6A8", "#E0A23F", "#E0673F", "#3FA0E0", "#A23FE0", "#E03F8D"];

  // Tile Z_CC cycles (each Z_TG scheduled slots + trailing US) across the
  // 24h view -- capped by the view width, not by Z_CC itself, so a large
  // Z_CC just tiles as many full/partial cycles as fit visually.
  let blocks = [];
  let cursor = 0;
  let guard = 0;
  while (cursor < 24 && guard++ < 48) {
    for (let g = 0; g < rc.Z_TG && cursor < 24; g++) {
      blocks.push({ start: cursor, hours: Math.min(ssHours, 24 - cursor), label: `G${g + 1}`, color: groupPalette[g % groupPalette.length], type: "charge" });
      cursor += ssHours;
    }
    if (usHours > 0 && cursor < 24) {
      blocks.push({ start: cursor, hours: Math.min(usHours, 24 - cursor), label: "US", type: "us" });
      cursor += usHours;
    }
    if (ssHours <= 0 && usHours <= 0) break; // guard against a zero-length cycle
  }

  const xOf = (h) => h * hourW;
  const hourTicks = [0, 6, 12, 18, 24];

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} style={{ display: "block" }}>
      {/* hour gridlines */}
      {hourTicks.map(h => (
        <g key={h}>
          <line x1={xOf(h)} y1={trackY - 4} x2={xOf(h)} y2={trackY + trackH + 4} stroke="var(--border)" strokeWidth="1" />
          <text x={xOf(h)} y={trackY + trackH + 18} fontSize="10.5" fill="var(--text-muted)" textAnchor={h === 24 ? "end" : (h === 0 ? "start" : "middle")}>
            {String(h).padStart(2, "0")}:00
          </text>
        </g>
      ))}

      {/* base track (full charging window, accent) */}
      <rect x={0} y={trackY} width={W} height={trackH} rx={6} fill="var(--ev-bg)" stroke="var(--c-accent)" strokeDasharray="3 3" strokeWidth="1" />

      {/* scheduled-group (SS) + unscheduled (US) blocks, tiled per cycle */}
      {blocks.map((b, i) => (
        <g key={i}>
          <rect x={xOf(b.start)} y={trackY} width={Math.max(0, xOf(b.hours))} height={trackH} rx={4}
            fill={b.type === "us" ? "none" : b.color}
            stroke={b.type === "us" ? "var(--text-muted)" : "none"}
            strokeDasharray={b.type === "us" ? "2 2" : undefined} opacity="0.88" />
          {xOf(b.hours) > 20 && (
            <text x={xOf(b.start) + xOf(b.hours) / 2} y={trackY + trackH / 2 + 4} fontSize="9.5"
              fill={b.type === "us" ? "var(--text-muted)" : "#fff"} textAnchor="middle" fontWeight="600">
              {b.label}
            </text>
          )}
        </g>
      ))}

      {/* legend */}
      <g transform={`translate(0, ${trackY + trackH + 28})`}>
        <rect x={0} y={0} width={12} height={12} rx={3} fill={groupPalette[0]} opacity="0.88" />
        <text x={18} y={10} fontSize="11" fill="var(--text)">{tr(lang, "Scheduled group (SS)", "Grup terjadwal (SS)")}</text>
        <rect x={190} y={0} width={12} height={12} rx={3} fill="none" stroke="var(--text-muted)" strokeDasharray="2 2" />
        <text x={208} y={10} fontSize="11" fill="var(--text)">{tr(lang, "Unscheduled (US)", "Tidak terjadwal (US)")}</text>
      </g>
    </svg>
  );
}

// ---------- Tab 1: Fleet & Charging (merged Fleet Profile + Charging Strategy) ----------
function Tab1FleetCharging({ s, set, lang, expertMode, sizing, veh }) {
  // v1.8 (2026-07-17): Ritase-Cycle Engine (§10 CALCULATION_ENGINE.md)
  // replaces the pre-v1.8 shift/fixed/opportunity Fleet Plan model. null
  // until an EV vehicle is selected (Screen 2) and Ritase Distance
  // (Operation tab) is resolvable.
  const rc = sizing && sizing.ritaseCycle;

  // ---- Charging Strategy portion (merged from former Tab2ChargingStrategy) ----
  // v1.8.2 (2026-07-18): chargingType/chargerRatingKw/voltageLevel are now
  // AUTO-COMPUTED from the Charging Strategy card's SS input (Charging
  // Requirement Engine, data.jsx computeChargingRequirement) -- s.chargingType
  // etc. are nullable Expert-Mode overrides now, resolved by computeSizing
  // into sizing.chargingType/.chargerRatingKw/.voltageLevel. Read those
  // (not s.* directly) so Simple Mode always reflects the live computed
  // value, matching the pattern already used for chargerRatio/ecActual/etc.
  const chargingTypeResolved = sizing ? sizing.chargingType : (s.chargingType ?? "dc");
  const onPickType = (type) => set("chargingType", type);
  const ov = s.screen4_valueOverrides || {};

  return (
    <div>
      <div className="grid-3">
        <Field en="Fleet Size" id="Jumlah Armada">
          <AffixInput value={fmt.num(s.fleetSize)} suffix={tr(lang, "vehicles", "kendaraan")}
            onChange={v => set("fleetSize", Number(v.replace(/\D/g, "")) || 0)} />
        </Field>
        <Field en="Daily Mileage" id="Jarak Tempuh Harian"
          helpEn={rc ? "Computed by the Ritase-Cycle Engine -- edit Ritase Distance on the Operation tab to change this." : "Raw input -- becomes a computed, read-only value once an EV vehicle and Ritase Distance (Operation tab) are both set."}
          help={rc ? "Dihitung oleh Mesin Siklus Ritase -- ubah Jarak Ritase di tab Operasi untuk mengubah ini." : "Input mentah -- menjadi nilai terhitung (read-only) setelah kendaraan EV dan Jarak Ritase (tab Operasi) keduanya diisi."}>
          {rc ? (
            <div className="affix readonly">
              <span className="fix pre">📏</span>
              <input value={`${fmt.num(Math.round(sizing.dailyMileage))} km/hari (computed)`} readOnly />
            </div>
          ) : (
            <AffixInput value={fmt.num(s.dailyMileageKm)} suffix="km/hari"
              onChange={v => set("dailyMileageKm", Number(v.replace(/\D/g, "")) || 0)} />
          )}
        </Field>
        <Field en="Operating Days/Year" id="Hari Operasi/Tahun">
          <AffixInput value={fmt.num(s.operatingDaysPerYear)} suffix={tr(lang, "days/yr", "hari/thn")}
            onChange={v => set("operatingDaysPerYear", Number(v.replace(/\D/g, "")) || 0)} />
        </Field>
      </div>

      <div className="row-full" style={{ marginTop: 14 }}>
        <SliderField en="Payload Utilization" id="Utilisasi Muatan" min={0} max={100}
          value={s.payloadPct ?? 50} onChange={v => set("payloadPct", v)} unit="%" />
      </div>

      {expertMode && (
        <div className="row-full" style={{ marginTop: 14 }}>
          <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", marginBottom: 10 }}>
            <input type="checkbox" checked={!!s.useLfLmrRefinement}
              onChange={e => set("useLfLmrRefinement", e.target.checked)} />
            {tr(lang,
              "Refine payload utilization via Load Factor x Loaded-Mile Ratio",
              "Sempurnakan utilisasi muatan via Load Factor x Loaded-Mile Ratio")}
          </label>
          {s.useLfLmrRefinement && (
            <div className="grid-2">
              <Field en="Load Factor" id="Load Factor"
                helpEn="% of rated payload carried when the vehicle is loaded. International research default (Flock Freight/ICCT-style studies) — checked for an Indonesia-specific figure 2026-06-30, none found publicly; still flagged as an assumption pending real fleet data."
                help="% kapasitas muatan terisi saat kendaraan bermuatan. Default riset internasional (studi gaya Flock Freight/ICCT) — telah diperiksa untuk angka khusus Indonesia 2026-06-30, belum ditemukan secara publik; tetap ditandai sebagai asumsi menunggu data armada riil.">
                <AffixInput value={fmt.num(s.loadFactorPct ?? 75)} suffix="%"
                  onChange={v => set("loadFactorPct", Number(v.replace(/[^\d.]/g, "")) || 0)} />
              </Field>
              <Field en="Loaded-Mile Ratio" id="Loaded-Mile Ratio"
                helpEn="% of total distance driven loaded vs. empty-return legs. Recalibrated 2026-06-30 (70% → 65%) using World Bank Indonesia freight-logistics research — Indonesian backhauls run ≥70% empty by volume due to the eastbound/westbound trade imbalance, worse than the US-sourced figure the prior default was based on. Still an approximation, not a directly-measured Indonesian LMR statistic."
                help="% total jarak yang ditempuh bermuatan vs. perjalanan kembali kosong. Dikalibrasi ulang 2026-06-30 (70% → 65%) menggunakan riset logistik angkutan Bank Dunia Indonesia — perjalanan kembali (backhaul) Indonesia berjalan ≥70% kosong berdasarkan volume akibat ketidakseimbangan perdagangan timur-barat, lebih buruk dari angka bersumber AS yang menjadi dasar default sebelumnya. Masih merupakan pendekatan, bukan statistik LMR Indonesia yang diukur langsung.">
                <AffixInput value={fmt.num(s.loadedMileRatioPct ?? 65)} suffix="%"
                  onChange={v => set("loadedMileRatioPct", Number(v.replace(/[^\d.]/g, "")) || 0)} />
              </Field>
            </div>
          )}

          <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", marginTop: 14 }}>
            <input type="checkbox" checked={!!s.usePhysicsPayloadFactors}
              onChange={e => set("usePhysicsPayloadFactors", e.target.checked)} />
            {tr(lang,
              "Derive empty/full energy split from vehicle physics (mass, drag) instead of a flat ±15%",
              "Turunkan pembagian energi kosong/penuh dari fisika kendaraan (massa, drag), bukan ±15% rata")}
            <InfoHint note={tr(lang,
              "Replaces the catalog-wide flat empty/full spread with a per-vehicle figure derived from rolling resistance + aerodynamic drag (Crr x mass x g + 0.5 x rho x CdA x v²), using each vehicle's real curb weight and payload capacity. A heavier vehicle relative to its own mass shows a wider empty-to-full swing than a lighter one; the catalog-wide 50%-laden anchor (this vehicle's own energy figure) is unchanged either way. Falls back to the flat ±15% for any vehicle missing a curb weight. Terrain/grade is NOT part of this — that stays the separate Terrain input's job, so the two don't double-count.",
              "Mengganti sebaran kosong/penuh rata di seluruh katalog dengan angka per kendaraan yang diturunkan dari rolling resistance + drag aerodinamis (Crr x massa x g + 0,5 x rho x CdA x v²), menggunakan berat kosong dan kapasitas muatan riil tiap kendaraan. Kendaraan yang lebih berat relatif terhadap massanya sendiri menunjukkan ayunan kosong-ke-penuh yang lebih lebar dibanding yang lebih ringan; jangkar 50%-bermuatan di seluruh katalog (angka energi kendaraan ini sendiri) tetap sama pada kedua kasus. Kembali ke ±15% rata untuk kendaraan mana pun yang tidak punya berat kosong. Medan/kemiringan TIDAK termasuk di sini — itu tetap tugas input Medan terpisah, agar keduanya tidak dihitung ganda.")} />
          </label>
        </div>
      )}

      <div className="row-full" style={{ marginTop: 18 }}>
        <Field en="Charging Strategy — Proposed Cycle & Group Count" id="Strategi Pengisian — Usulan Jumlah Siklus & Grup"
          helpEn="Propose how many charging cycles run per day, and how many scheduled groups share each cycle's charging slots. Both are validated against ritase physics (Operation tab) and depot capacity -- if a proposal doesn't fit, the platform falls back to the maximum feasible value and flags it in Results."
          help="Usulkan berapa siklus pengisian daya berjalan per hari, dan berapa grup terjadwal berbagi slot pengisian tiap siklus. Keduanya divalidasi terhadap fisika ritase (tab Operasi) dan kapasitas depot -- jika usulan tidak sesuai, platform otomatis menggunakan nilai maksimum yang layak dan menandainya di Hasil.">
          <div className="grid-3">
            <div>
              <label style={{ fontSize: 12, color: "var(--text-muted)" }}>{tr(lang, "Proposed Cycles/Day", "Usulan Siklus/Hari")}</label>
              <AffixInput value={fmt.num(s.proposedCycleCount)} suffix={tr(lang, "cycles", "siklus")}
                onChange={v => set("proposedCycleCount", Math.max(1, Number(v.replace(/\D/g, "")) || 1))} />
            </div>
            <div>
              <label style={{ fontSize: 12, color: "var(--text-muted)" }}>{tr(lang, "Proposed Groups/Cycle", "Usulan Grup/Siklus")}</label>
              <AffixInput value={fmt.num(s.proposedGroupCount)} suffix={tr(lang, "groups", "grup")}
                onChange={v => set("proposedGroupCount", Math.max(1, Number(v.replace(/\D/g, "")) || 1))} />
            </div>
            <div>
              <label style={{ fontSize: 12, color: "var(--text-muted)" }}>{tr(lang, "Scheduled Charging/Swap Shift (SS)", "Shift Pengisian/Swap Terjadwal (SS)")}</label>
              {/* v1.9.3: one merged dropdown drives SS for every vehicle type
                  (charge: group = vehicles, swap: group = battery packs) --
                  no longer gated by the selected vehicle's isSwapVeh, so the
                  Gantt chart below can be explored for either scenario
                  regardless of which vehicle is currently picked on Screen 2. */}
              <Select value={String(s.chargeSessionMinutes ?? 90)} onChange={v => set("chargeSessionMinutes", Number(v))}
                options={[3, 5, 7, 10, 40, 50, 60, 70, 80, 90].map(m => ({ value: String(m), label: `${m} min` }))} />
            </div>
          </div>
        </Field>
      </div>

      {rc && rc.Z_RC === 0 && (
        <div className="row-full" style={{ marginTop: 12 }}>
          <WarnHint label={tr(lang, "EV Range Too Short", "Jangkauan EV Terlalu Pendek")}
            note={tr(lang,
              "The selected EV's usable range is shorter than the Ritase Distance (Operation tab) -- it cannot complete one round trip per charge, so the schedule below computes to 0.",
              "Jangkauan terpakai EV terpilih lebih pendek dari Jarak Ritase (tab Operasi) -- tidak bisa menyelesaikan satu perjalanan pulang-pergi per pengisian, sehingga jadwal di bawah dihitung 0.")} />
        </div>
      )}
      {rc ? (
        <div className="row-full" style={{ marginTop: 14 }}>
          <div>
            <DayTimelineSVG ritaseCycle={rc} lang={lang} />
          </div>
          <div className="empty-window-callout" style={{ marginTop: 12 }}>
            <div>
              <Tr en="Charging cycles/day (Z_CC)" id="Siklus pengisian/hari (Z_CC)" />: <b>{rc.Z_CC}</b>
              {!rc.cycleGateOk && (
                <span style={{ color: "var(--c-danger, #EF4444)" }}>
                  {" "}({tr(lang, `proposed ${rc.PZ_CC} didn't fit the ritase drive+charge time -- auto-adjusted`, `usulan ${rc.PZ_CC} tidak sesuai waktu jalan+isi daya ritase -- disesuaikan otomatis`)})
                </span>
              )}
            </div>
            <div>
              <Tr en="Cycle length" id="Panjang siklus" />: <b>{(rc.CC / 60).toFixed(1)} {tr(lang, "hrs", "jam")}</b>
            </div>
            <div>
              <Tr en="Scheduled groups/cycle (Z_TG)" id="Grup terjadwal/siklus (Z_TG)" />: <b>{rc.Z_TG}</b>
              {!rc.groupGateOk && (
                <span style={{ color: "var(--c-danger, #EF4444)" }}>
                  {" "}({tr(lang, `proposed ${rc.PZ_TG} exceeds ${rc.Z_TPG} slots/cycle -- auto-adjusted`, `usulan ${rc.PZ_TG} melebihi ${rc.Z_TPG} slot/siklus -- disesuaikan otomatis`)})
                </span>
              )}
            </div>
            <div>
              <Tr en="Unscheduled (opportunistic) window" id="Jendela tidak terjadwal (oportunistik)" />: <b>{(rc.US / 60).toFixed(1)} {tr(lang, "hrs/cycle", "jam/siklus")}</b>
            </div>
          </div>
        </div>
      ) : (
        <div className="empty-window-callout" style={{ marginTop: 12 }}>
          {/* v1.8.4 (2026-07-19): was one generic message regardless of which
              precondition was missing -- now names the specific one (no EV /
              no Ritase Distance / EV has no resolvable range), same
              ritaseCycleBlockedReason helper Screen 3's Annual Mileage uses. */}
          {(() => {
            const reason = window.ritaseCycleBlockedReason(s);
            if (reason === "no-rd") return (
              <Tr en="Set a Ritase Distance (Operation tab) to compute the charging cycle schedule."
                  id="Atur Jarak Ritase (tab Operasi) untuk menghitung jadwal siklus pengisian daya." />
            );
            if (reason === "no-range") return (
              <Tr en="The selected EV has no resolvable battery/energy-consumption spec -- cannot compute the charging cycle schedule."
                  id="EV terpilih tidak memiliki spesifikasi baterai/konsumsi energi yang bisa dihitung -- jadwal siklus pengisian daya tidak dapat dihitung." />
            );
            return (
              <Tr en="Set an EV vehicle (Vehicle Selection) to compute the charging cycle schedule."
                  id="Atur kendaraan EV (Pemilihan Kendaraan) untuk menghitung jadwal siklus pengisian daya." />
            );
          })()}
        </div>
      )}

      {expertMode && (
        <div style={{ marginTop: 18 }}>
          <CollapsibleSection title={tr(lang, "Expert: Energy Consumption Model", "Ahli: Model Konsumsi Energi")} defaultOpen>
            <div className="grid-3">
              <Field en="EC (empty load)" id="EC (muatan kosong)"
                helpEn="Default = vehicle EC × 0.85" help="Default = EC kendaraan × 0,85">
                <AffixInput value={String(s.ecEmptyOverride ?? Number((sizing?.ecEmptyDefault ?? 0).toFixed(3)))}
                  suffix="kWh/km"
                  onChange={v => { const n = Number(v); set("ecEmptyOverride", v === "" || isNaN(n) ? null : n); }} />
                <ValueFlag defaultValue={sizing ? Number(sizing.ecEmptyDefault.toFixed(3)) : null} currentValue={s.ecEmptyOverride}
                  onReset={() => set("ecEmptyOverride", null)} />
              </Field>
              <Field en="EC (full load)" id="EC (muatan penuh)"
                helpEn="Default = vehicle EC × 1.15" help="Default = EC kendaraan × 1,15">
                <AffixInput value={String(s.ecFullOverride ?? Number((sizing?.ecFullDefault ?? 0).toFixed(3)))}
                  suffix="kWh/km"
                  onChange={v => { const n = Number(v); set("ecFullOverride", v === "" || isNaN(n) ? null : n); }} />
                <ValueFlag defaultValue={sizing ? Number(sizing.ecFullDefault.toFixed(3)) : null} currentValue={s.ecFullOverride}
                  onReset={() => set("ecFullOverride", null)} />
              </Field>
              <Field en="Charging Efficiency" id="Efisiensi Pengisian Daya"
                helpEn="Default 92% — accounts for AC/DC conversion losses." help="Default 92% — memperhitungkan rugi konversi AC/DC.">
                <AffixInput value={fmt.num(Math.round((s.chargingEfficiencyOverride ?? 0.92) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); set("chargingEfficiencyOverride", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={92} currentValue={s.chargingEfficiencyOverride != null ? Math.round(s.chargingEfficiencyOverride * 100) : null}
                  onReset={() => set("chargingEfficiencyOverride", null)} />
              </Field>
            </div>
          </CollapsibleSection>
        </div>
      )}

      <div className="row-full" style={{ marginTop: 24, borderTop: "1px solid var(--border)", paddingTop: 18 }}>
        <Field en="Charging Type" id="Tipe Pengisian Daya"
          helpEn="Auto-computed from the charging session duration (SS, above) and this vehicle's battery size -- the power needed to top up 20%→80% SOC within that time picks the smallest adequate charging type. Expert Mode: click a card to override."
          help="Dihitung otomatis dari durasi sesi pengisian (SS, di atas) dan ukuran baterai kendaraan ini -- daya yang dibutuhkan untuk mengisi 20%→80% SOC dalam waktu tersebut memilih tipe pengisian terkecil yang mencukupi. Mode Ahli: klik kartu untuk mengganti.">
          <div className="option-card-grid" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
            {CHARGING_TYPE_OPTIONS.map(o => (
              <div key={o.id} className={"option-card" + (chargingTypeResolved === o.id ? " active" : "") + (expertMode ? "" : " readonly")}
                onClick={() => { if (expertMode) onPickType(o.id); }}>
                <div className="oc-icon">{o.icon}</div>
                <div className="oc-label">{tr(lang, o.en, o.idLbl)}</div>
                <div className="oc-desc">{o.sub}</div>
              </div>
            ))}
          </div>
          {expertMode ? (
            <ValueFlag defaultValue={sizing ? sizing.chargingTypeDefault : null} currentValue={s.chargingType ?? null}
              onReset={() => set("chargingType", null)} />
          ) : (
            <span className="assumption-tag">{tr(lang, "Computed", "Terhitung")}</span>
          )}
        </Field>
      </div>

      {/* v1.8.6 (2026-07-19): "Charger Rating"/"Nozzles per Vehicle
          (simultaneous)"/"Voltage Level" row removed from display below the
          Charging Type card per Rija's explicit request -- all three are
          fully auto-computed from Charging Type + the Charging Strategy
          card's SS input (Charging Requirement Engine, data.jsx
          computeChargingRequirement), and showing them alongside the
          already-visible Charging Type card was redundant/confusing.
          Underlying computation is UNCHANGED -- sizing.chargerRatingKw/
          .voltageLevel/.chargingRequirement.nozzlesPerVehicle still feed
          CAPEX/dispenser sizing exactly as before; chargerRatingKw/
          voltageLevel/nozzlesPerVehicleOverrideA/B remain valid Expert-Mode
          override keys in the state model if ever needed again, just not
          reachable from this UI anymore. */}

      {expertMode && (
        <div className="row-full" style={{ marginTop: 14 }}>
          <Field en="Existing Site Power Available" id="Daya Lokasi Tersedia" opt>
            <AffixInput value={s.siteAvailableKva != null ? fmt.num(s.siteAvailableKva) : ""}
              suffix="kVA"
              onChange={v => { const c = v.replace(/\D/g, ""); set("siteAvailableKva", c === "" ? null : Number(c)); }} />
          </Field>
        </div>
      )}

      {/* v1.8.3 (2026-07-18): "Charging Idle Time & Vehicles per Charging
          Bay" panel removed from display per Rija's explicit request --
          every field in it was now either a direct mirror of the Charging
          Strategy card's SS input (Charging Session Duration) or a fully
          auto-computed value (Throughput-Required Chargers), redundant
          clutter once Charging Type/Rating/Nozzles/Voltage became
          auto-computed too. The underlying computation is UNCHANGED --
          chargerRatio/throughputChargerCount/chargerCount still compute
          exactly as before (computeSizing, data.jsx); ov["tab2.chargerRatio"]
          remains a valid override key if ever needed again, just not
          reachable from this UI anymore. */}

      {expertMode && (
        <div style={{ marginTop: 18 }}>
          <CollapsibleSection title={tr(lang, "Expert: Power Sizing Factors", "Ahli: Faktor Perhitungan Daya")} defaultOpen>
            <div className="grid-3">
              <Field en="Utilization Factor" id="Faktor Utilisasi"
                helpEn="Share of available window the chargers are actually drawing power."
                help="Porsi jendela waktu yang benar-benar digunakan charger.">
                <AffixInput value={fmt.num(Math.round((ov["tab2.utilizationFactor"] ?? sizing?.utilizationFactorDefault ?? 0) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); setOv(set, s, "tab2.utilizationFactor", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={sizing ? Math.round(sizing.utilizationFactorDefault * 100) : null}
                  currentValue={ov["tab2.utilizationFactor"] != null ? Math.round(ov["tab2.utilizationFactor"] * 100) : null}
                  onReset={() => setOv(set, s, "tab2.utilizationFactor", null)} />
              </Field>
              <Field en="Diversity Factor" id="Faktor Diversitas"
                helpEn="Share of chargers drawing peak power simultaneously."
                help="Porsi charger yang menarik daya puncak secara bersamaan.">
                <AffixInput value={fmt.num(Math.round((ov["tab2.diversityFactor"] ?? sizing?.diversityFactorDefault ?? 0) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); setOv(set, s, "tab2.diversityFactor", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={sizing ? Math.round(sizing.diversityFactorDefault * 100) : null}
                  currentValue={ov["tab2.diversityFactor"] != null ? Math.round(ov["tab2.diversityFactor"] * 100) : null}
                  onReset={() => setOv(set, s, "tab2.diversityFactor", null)} />
              </Field>
              <Field en="Power Factor" id="Faktor Daya"
                helpEn="Default 95% — ratio of real to apparent power." help="Default 95% — rasio daya nyata terhadap daya semu.">
                <AffixInput value={fmt.num(Math.round((s.powerFactorOverride ?? 0.95) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); set("powerFactorOverride", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={95} currentValue={s.powerFactorOverride != null ? Math.round(s.powerFactorOverride * 100) : null}
                  onReset={() => set("powerFactorOverride", null)} />
              </Field>
            </div>

            <div className="row-full" style={{ marginTop: 14 }}>
              <Field en="Demand Charge Modeling" id="Pemodelan Biaya Permintaan">
                <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                  <input type="checkbox" checked={!!s.demandChargeEnabled}
                    onChange={e => set("demandChargeEnabled", e.target.checked)} />
                  {tr(lang, "Apply estimated WBP (peak-hour) surcharge to EV energy cost", "Terapkan estimasi tambahan biaya WBP (jam sibuk) pada biaya energi EV")}
                  <span className="assumption-tag">{tr(lang, "EST", "EST")}</span>
                  <InfoHint note={tr(lang,
                    "PLN's time-of-use tariffs for medium-voltage business/industrial customers (e.g. B-3, I-3/I-4) can apply a higher rate during the Waktu Beban Puncak (WBP) peak window, commonly 17:00–22:00. If the fleet charges during this window, electricity cost is higher than the flat tariff used elsewhere in this tool. This section lets you apply a simplified estimated surcharge based on how much of the charging schedule overlaps WBP hours — a full per-hour demand-charge model is planned for a future version.",
                    "Tarif PLN berbasis waktu untuk pelanggan bisnis/industri tegangan menengah (mis. B-3, I-3/I-4) dapat mengenakan tarif lebih tinggi pada jendela Waktu Beban Puncak (WBP), umumnya 17:00–22:00. Jika armada mengisi daya pada jendela ini, biaya listrik menjadi lebih tinggi dari tarif flat yang digunakan di tempat lain pada alat ini. Bagian ini memungkinkan Anda menerapkan estimasi tambahan biaya sederhana berdasarkan seberapa besar jadwal pengisian bersinggungan dengan jam WBP — model biaya permintaan per jam yang lengkap direncanakan untuk versi mendatang.")} />
                </label>
                {s.demandChargeEnabled && (
                  <div style={{ marginTop: 10, maxWidth: 420 }}>
                    <Select value={s.demandChargeProfileId} onChange={v => set("demandChargeProfileId", v)}
                      options={window.DEMAND_CHARGE_PROFILES.map(p => ({ value: p.id, label: tr(lang, p.labelEn, p.labelId) }))} />
                    <div style={{ marginTop: 6, fontSize: 12.5, color: "var(--text-muted)", lineHeight: 1.5 }}>
                      {tr(lang, window.findDemandChargeProfile(s.demandChargeProfileId).descEn, window.findDemandChargeProfile(s.demandChargeProfileId).descId)}
                      {" "}{tr(lang,
                        `Estimated effect: +${Math.round(window.findDemandChargeProfile(s.demandChargeProfileId).wbpSharePct / 100 * (window.findDemandChargeProfile(s.demandChargeProfileId).wbpMultiplier - 1) * 100)}% on lifetime EV energy cost.`,
                        `Efek estimasi: +${Math.round(window.findDemandChargeProfile(s.demandChargeProfileId).wbpSharePct / 100 * (window.findDemandChargeProfile(s.demandChargeProfileId).wbpMultiplier - 1) * 100)}% pada biaya energi EV seumur hidup.`)}
                    </div>
                  </div>
                )}
              </Field>
            </div>
          </CollapsibleSection>
        </div>
      )}
    </div>
  );
}

// ---------- Tab 3: Growth (v1.7.7: Existing Assets / Reusability sub-blocks
// removed -- they only ever rendered for a non-Greenfield project type,
// which no longer exists on this platform) ----------
function Tab3GrowthAssets({ s, set, lang }) {
  const current = s.fleetSize || 0;
  const planned = s.plannedFleet5yr;
  const growthRate = (planned != null && current > 0)
    ? (Math.pow(planned / current, 1 / 5) - 1) * 100
    : null;

  const numField = (key, suffix) => ({
    value: s[key] != null ? fmt.num(s[key]) : "",
    suffix,
    onChange: v => { const c = v.replace(/\D/g, ""); set(key, c === "" ? null : Number(c)); },
  });

  return (
    <div>
      <div className="grid-3">
        <Field en="Current Fleet Size" id="Jumlah Armada Saat Ini">
          <div className="affix readonly">
            <input value={fmt.num(current)} readOnly />
            <span className="fix suf">{tr(lang, "vehicles", "kendaraan")}</span>
          </div>
        </Field>
        <Field en="Planned Fleet Size (5-Year)" id="Target Armada (5 Tahun)" opt>
          <AffixInput {...numField("plannedFleet5yr", tr(lang, "vehicles", "kendaraan"))} />
        </Field>
        <Field en="Annual Growth Rate" id="Tingkat Pertumbuhan Tahunan"
          helpEn="Auto-computed: ((Planned / Current)^(1/5) − 1) × 100%"
          help="Otomatis dihitung: ((Target / Saat Ini)^(1/5) − 1) × 100%">
          <div className="affix readonly">
            <input value={growthRate != null ? growthRate.toFixed(1) + "%" : "—"} readOnly />
          </div>
        </Field>
      </div>
    </div>
  );
}

// ---------- Tab 4: Sizing Engine ----------
function Tab4SizingEngine({ s, set, lang, expertMode, sizing }) {
  if (!sizing) {
    return (
      <WarnHint label={tr(lang, "Select EV", "Pilih EV")}
        note={tr(lang, "Select an EV vehicle in Vehicle Selection (Screen 2) to compute infrastructure sizing.", "Pilih kendaraan EV di Pemilihan Kendaraan (Layar 2) untuk menghitung perhitungan infrastruktur.")} />
    );
  }

  const ov = s.screen4_valueOverrides || {};
  const readinessMsg = sizing.readiness === "green"
    ? tr(lang, "Existing site power is sufficient — no upgrade required.", "Daya lokasi eksisting mencukupi — tidak perlu upgrade.")
    : sizing.existingKva == null
      ? tr(lang, "No site power data provided — readiness unknown.", "Data daya lokasi belum diisi — kesiapan tidak diketahui.")
      : sizing.readiness === "yellow"
        ? tr(lang, `Minor upgrade needed (+${sizing.increasePct.toFixed(0)}%).`, `Perlu upgrade kecil (+${sizing.increasePct.toFixed(0)}%).`)
        : tr(lang, `Major upgrade or new connection required (+${sizing.increasePct.toFixed(0)}%).`, `Perlu upgrade besar atau sambungan baru (+${sizing.increasePct.toFixed(0)}%).`);

  const metrics = [
    { en: "EC Actual",                    id: "EC Aktual",                       val: sizing.ecActual.toFixed(3) + " kWh/km" },
    { en: "Daily Fleet Energy",           id: "Energi Armada Harian",            val: fmt.num(Math.round(sizing.dailyFleetEnergy)) + " kWh/hari" },
    { en: "Charging Window",              id: "Jendela Pengisian Daya",          val: sizing.chargingWindowHours + " " + tr(lang, "hrs", "jam") },
    { en: "Required Charging Power",      id: "Daya Pengisian Dibutuhkan",       val: fmt.num(Math.round(sizing.requiredPowerKw)) + " kW" },
    { en: "Adjusted Required Power",      id: "Daya Dibutuhkan (Disesuaikan)",   val: fmt.num(Math.round(sizing.adjustedRequiredPowerKw)) + " kW" },
    { en: "Charger Count",                id: "Jumlah Charger",                  val: fmt.num(sizing.chargerCount) + " " + tr(lang, "units", "unit") },
    { en: "Total Charging Load",          id: "Total Beban Pengisian",           val: fmt.num(Math.round(sizing.totalChargingLoadKw)) + " kW" },
    { en: "Recommended Transformer Size", id: "Ukuran Trafo Rekomendasi",        val: fmt.num(Math.round(sizing.transformerKva)) + " kVA" },
  ];

  // v1.21: in Simple Mode, once Depot Design has posted a result, the
  // standalone Sizing Engine's own numbers are redundant noise — Depot
  // Design's totals are what actually feeds Results. Simple Mode collapses
  // this whole tab to a one-line pointer; Expert Mode still shows the full
  // independent estimate (useful for sanity-checking Depot Design's output).
  if (s.depotBom && !expertMode) {
    return (
      <div className="audit-caption" style={{ textTransform: "none" }}>
        {tr(lang, "Sizing Engine hidden — Depot Design is active", "Mesin Perhitungan disembunyikan — Desain Depot aktif")}
        <InfoHint note={tr(lang,
          "Depot Design (Screen 4 → Depot Design tab) is sizing infrastructure for this comparison and its CAPEX/OPEX is what feeds the Results screen — this tab's own Sizing Engine estimate is hidden in Simple Mode. Switch to Expert Mode to see it as a reference cross-check.",
          "Desain Depot (Layar 4 → tab Desain Depot) sedang menentukan ukuran infrastruktur untuk perbandingan ini dan CAPEX/OPEX-nya menjadi sumber Layar Hasil — estimasi Mesin Perhitungan tab ini disembunyikan di Mode Sederhana. Beralih ke Mode Ahli untuk melihatnya sebagai cek silang referensi.")} />
      </div>
    );
  }

  return (
    <div>
      {s.depotBom && (
        <div className="audit-caption" style={{ textTransform: "none" }}>
          {tr(lang, "Reference only — Depot Design feeds Results", "Referensi saja — Desain Depot menjadi sumber Hasil")}
          <InfoHint note={tr(lang,
            "Depot Design (Screen 4 → Depot Design tab) is sizing infrastructure for this comparison and its CAPEX/OPEX is what feeds the Results screen. The numbers below are TCO's own independent Sizing Engine estimate, shown for reference only — the two use different buffer assumptions (redundancy/growth margin) and won't match exactly.",
            "Desain Depot (Layar 4 → tab Desain Depot) sedang menentukan ukuran infrastruktur untuk perbandingan ini dan CAPEX/OPEX-nya menjadi sumber Layar Hasil. Angka di bawah adalah estimasi independen Mesin Perhitungan milik TCO sendiri, ditampilkan sebagai referensi saja — keduanya menggunakan asumsi margin (redundansi/pertumbuhan) yang berbeda dan tidak akan persis sama.")} />
        </div>
      )}
      <div className="spec-grid">
        {metrics.map((m, i) => (
          <div className="spec-cell" key={i}>
            <div className="sk">{tr(lang, m.en, m.id)}</div>
            <div className="sv">{m.val}</div>
          </div>
        ))}
      </div>

      <div style={{ marginTop: 14 }}>
        <InfraReadinessIndicator status={sizing.readiness} message={readinessMsg} />
      </div>

      <div style={{ marginTop: 14 }}>
        <CollapsibleSection title={tr(lang, "How was this calculated?", "Bagaimana ini dihitung?")}>
          <table className="sstt-table">
            <tbody>
              <tr><td>{tr(lang, "Redundancy Factor", "Faktor Redundansi")}</td><td>{sizing.redundancyFactor.toFixed(2)}×</td></tr>
              <tr><td>{tr(lang, "Diversity Factor", "Faktor Diversitas")}</td><td>{sizing.diversityFactor.toFixed(2)}×</td></tr>
              <tr><td>{tr(lang, "Growth Margin", "Margin Pertumbuhan")}</td><td>{sizing.growthMargin.toFixed(2)}×</td></tr>
              <tr><td>{tr(lang, "Infra Multiplier", "Pengali Infrastruktur")}</td><td>{sizing.infraMultiplier.toFixed(2)}×</td></tr>
              <tr><td>{tr(lang, "Utilization Factor", "Faktor Utilisasi")}</td><td>{(sizing.utilizationFactor * 100).toFixed(0)}%</td></tr>
              <tr><td>{tr(lang, "Power Factor", "Faktor Daya")}</td><td>{(sizing.powerFactor * 100).toFixed(0)}%</td></tr>
              <tr><td>{tr(lang, "Charging Efficiency", "Efisiensi Pengisian Daya")}</td><td>{(sizing.chargingEfficiency * 100).toFixed(0)}%</td></tr>
            </tbody>
          </table>
          <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 8, display: "flex", alignItems: "center" }}>
            {tr(lang, "Multiplier source", "Sumber pengali")}
            <InfoHint note={tr(lang,
              "Multiplier values come from this ecosystem and project type's profile (infra_profiles.js). Adjust them in Expert Mode on Tabs 2 and 4.",
              "Nilai pengali berasal dari profil ekosistem dan tipe proyek ini (infra_profiles.js). Sesuaikan di Mode Ahli pada Tab 2 dan 4.")} />
          </div>
        </CollapsibleSection>
      </div>

      {expertMode && (
        <div style={{ marginTop: 14 }}>
          <CollapsibleSection title={tr(lang, "Expert: Manual Overrides", "Ahli: Override Manual")} defaultOpen>
            <div className="grid-3">
              <Field en="Redundancy Factor" id="Faktor Redundansi">
                <AffixInput value={fmt.num(Math.round((ov["tab4.redundancyFactor"] ?? sizing.redundancyFactorDefault) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); setOv(set, s, "tab4.redundancyFactor", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={Math.round(sizing.redundancyFactorDefault * 100)}
                  currentValue={ov["tab4.redundancyFactor"] != null ? Math.round(ov["tab4.redundancyFactor"] * 100) : null}
                  onReset={() => setOv(set, s, "tab4.redundancyFactor", null)} />
              </Field>
              <Field en="Growth Margin" id="Margin Pertumbuhan">
                <AffixInput value={fmt.num(Math.round((ov["tab4.growthMargin"] ?? sizing.growthMarginDefault) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); setOv(set, s, "tab4.growthMargin", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={Math.round(sizing.growthMarginDefault * 100)}
                  currentValue={ov["tab4.growthMargin"] != null ? Math.round(ov["tab4.growthMargin"] * 100) : null}
                  onReset={() => setOv(set, s, "tab4.growthMargin", null)} />
              </Field>
              <Field en="Infra Multiplier" id="Pengali Infrastruktur">
                <AffixInput value={fmt.num(Math.round((ov["tab4.infraMultiplier"] ?? sizing.infraMultiplierDefault) * 100))} suffix="%"
                  onChange={v => { const n = Number(v.replace(/\D/g, "")); setOv(set, s, "tab4.infraMultiplier", isNaN(n) ? null : n / 100); }} />
                <ValueFlag defaultValue={Math.round(sizing.infraMultiplierDefault * 100)}
                  currentValue={ov["tab4.infraMultiplier"] != null ? Math.round(ov["tab4.infraMultiplier"] * 100) : null}
                  onReset={() => setOv(set, s, "tab4.infraMultiplier", null)} />
              </Field>
              <Field en="Charger Count Override" id="Override Jumlah Charger" opt>
                <AffixInput value={s.chargerCountOverride != null ? fmt.num(s.chargerCountOverride) : ""} suffix={tr(lang, "units", "unit")}
                  onChange={v => { const c = v.replace(/\D/g, ""); set("chargerCountOverride", c === "" ? null : Number(c)); }} />
              </Field>
              <Field en="Transformer Size Override" id="Override Ukuran Trafo" opt>
                <AffixInput value={s.transformerKvaOverride != null ? fmt.num(s.transformerKvaOverride) : ""} suffix="kVA"
                  onChange={v => { const c = v.replace(/\D/g, ""); set("transformerKvaOverride", c === "" ? null : Number(c)); }} />
              </Field>
            </div>
          </CollapsibleSection>
        </div>
      )}
    </div>
  );
}

// Depot's BOM categories (depot_floorplan/js/bom.js BOM_CATEGORY_KEYS) — not
// 1:1 with TCO's own A–E (audit finding #4 in the integration plan), so
// shown under depot's own category labels rather than forced into A–E.
const DEPOT_CATEGORY_ORDER = ["electrical", "site", "building", "other", "swap", "software"];
const DEPOT_CATEGORY_LABELS = {
  electrical: { en: "Electrical & Charging Equipment", id: "Peralatan Kelistrikan & Pengisian" },
  site:       { en: "Site (Civil + Utility)",            id: "Lokasi (Sipil + Utilitas)" },
  building:   { en: "Building",                          id: "Bangunan" },
  other:      { en: "Other (Fencing/Security/Signage)",  id: "Lainnya (Pagar/Keamanan/Marka)" },
  swap:       { en: "Battery Swap Station",               id: "Stasiun Penukaran Baterai" },
  software:   { en: "Software (CMS/EMS/Dashboard)",      id: "Perangkat Lunak (CMS/EMS/Dashboard)" },
};
const DEPOT_ITEM_LABELS = {
  dispensers: "Dispensers", transformer: "Transformer", bos: "Civil + Electrical Install",
  land: "Land", pavingRoad: "Road Paving", pavingBay: "Bay Paving", drainage: "Drainage",
  gridPermit: "Grid Connection / Permit", feeder: "Feeder Cable", building: "Building Structure",
  fencing: "Fencing", gates: "Gates", signage: "Signage", securitySystems: "Security Systems",
  swapRobots: "Swap Robots", swapBatteries: "Battery Packs", swapRack: "Battery Racks",
  cms: "CMS", ems: "EMS", dashboard: "Dashboard",
};
function DepotCapexBreakdown({ bom, includeFlags, lang }) {
  // Only show categories actually toggled into TCO scope inside the depot
  // tool's own BOM/Cost tab (default electrical-only) — bom.tcoCapex/tcoOpex
  // already reflect this filter; the per-category rows now match it too,
  // instead of showing all 6 categories regardless of scope.
  const shownKeys = DEPOT_CATEGORY_ORDER.filter(key => !includeFlags || includeFlags[key]);
  const hiddenKeys = DEPOT_CATEGORY_ORDER.filter(key => includeFlags && !includeFlags[key] && bom.byCategory[key] && (bom.byCategory[key].capex > 0 || bom.byCategory[key].opex > 0));
  return (
    <div>
      {shownKeys.map(key => {
        const cat = bom.byCategory[key];
        if (!cat || (cat.capex === 0 && cat.opex === 0)) return null;
        const items = Object.entries(cat.items || {}).filter(([, v]) => v).map(([k, v]) => ({ label: DEPOT_ITEM_LABELS[k] || k, value: v }));
        const lbl = DEPOT_CATEGORY_LABELS[key] || { en: key, id: key };
        return (
          <div className="capex-row" key={key}>
            <div className="capex-row-head">
              <span className="capex-cat-label">{tr(lang, lbl.en, lbl.id)}</span>
              <span className="capex-cat-total">{fmt.rp(cat.capex)} <span style={{ fontWeight: 400, fontSize: 11, color: "var(--text-muted)" }}>({tr(lang, "OPEX/yr", "OPEX/thn")} {fmt.rp(cat.opex)})</span></span>
            </div>
            {items.length > 0 && (
              <div className="capex-row-items">
                {items.map((it, i) => (
                  <div className="capex-item" key={i}><span>{it.label}</span><span>{fmt.rp(it.value)}</span></div>
                ))}
              </div>
            )}
          </div>
        );
      })}
      {hiddenKeys.length > 0 && (
        <div style={{ fontSize: 11, color: "var(--text-muted)", margin: "6px 0" }}>
          {tr(lang, "Not in TCO scope — managed and expended by Helio Sinar Energi, not VKTR: ", "Tidak dalam lingkup TCO — dikelola dan dibiayai oleh Helio Sinar Energi, bukan VKTR: ")}
          {hiddenKeys.map(k => tr(lang, DEPOT_CATEGORY_LABELS[k].en, DEPOT_CATEGORY_LABELS[k].id)).join(", ")}
        </div>
      )}
      <div className="capex-row" style={{ borderTop: "2px solid var(--border-strong)", marginTop: 8, paddingTop: 12 }}>
        <div className="capex-row-head">
          <span className="capex-cat-label" style={{ fontSize: 15 }}>{tr(lang, "Depot Design Total (in TCO scope)", "Total Desain Depot (dalam lingkup TCO)")}</span>
          <span className="capex-cat-total" style={{ fontSize: 16 }}>{fmt.rp(bom.tcoCapex)}</span>
        </div>
        <div className="capex-row-reason">
          {tr(lang, "OPEX/yr (in TCO scope): ", "OPEX/thn (dalam lingkup TCO): ") + fmt.rp(bom.tcoOpex)}
        </div>
      </div>
    </div>
  );
}

// ---------- Tab 5: CAPEX Breakdown ----------
function Tab5CapexBreakdown({ s, set, lang, expertMode, sizing, capex }) {
  if (!sizing || !capex) {
    return (
      <WarnHint label={tr(lang, "Select EV", "Pilih EV")}
        note={tr(lang, "Select an EV vehicle in Vehicle Selection (Screen 2) to compute the CAPEX breakdown.", "Pilih kendaraan EV di Pemilihan Kendaraan (Layar 2) untuk menghitung rincian CAPEX.")} />
    );
  }

  const naReason = tr(lang,
    "The selected EV uses battery swap only — charging equipment is not required.",
    "EV yang dipilih hanya menggunakan penukaran baterai — peralatan pengisian tidak diperlukan.");
  const budgetCap = s.infraBudgetCap;
  const depotActive = !!s.depotBom;
  const grandTotal = depotActive ? s.depotBom.tcoCapex : capex.tcoCapex;

  // The A–E breakdown below (CapexCategoryRow rows + salvage credit + its
  // own total line) renders the same JSX regardless of source; when Depot
  // Design is active it's tucked into a collapsed "reference only" section
  // instead of sitting inline next to depot's breakdown, so this page has
  // exactly one prominent total rather than two competing ones.
  const sizingEngineBreakdown = (
    <>
      <CapexCategoryRow category="A" label={tr(lang, "Charger Equipment", "Peralatan Pengisian Daya")}
        items={capex.A.items} total={capex.A.total} applicable={capex.chargeApplicable} reason={naReason} s={s} set={set} />
      <CapexCategoryRow category="B" label={tr(lang, "Electrical Infrastructure", "Infrastruktur Kelistrikan")}
        items={capex.B.items} total={capex.B.total} applicable={true} s={s} set={set} />
      <CapexCategoryRow category="C" label={tr(lang, "Civil Works", "Pekerjaan Sipil")}
        items={capex.C.items} total={capex.C.total} applicable={true} s={s} set={set} />
      <CapexCategoryRow category="D" label={tr(lang, "Utility Upgrade", "Upgrade Utilitas")}
        items={capex.D.items} total={capex.D.total} applicable={true} s={s} set={set} />
      <CapexCategoryRow category="E" label={tr(lang, "Software", "Perangkat Lunak")}
        items={capex.E.items} total={capex.E.total} applicable={true} s={s} set={set} />

      {capex.salvageCredit > 0 && (
        <div className="capex-row">
          <div className="capex-row-head">
            <span className="capex-cat-label">{tr(lang, "Salvage Credit (Replacement)", "Kredit Sisa Aset (Penggantian)")}</span>
            <span className="capex-cat-total">−{fmt.rp(capex.salvageCredit)}</span>
          </div>
          <div className="capex-row-reason">
            <span className="assumption-tag">{tr(lang, "Assumption: 5% of total CAPEX", "Asumsi: 5% dari total CAPEX")}</span>
          </div>
        </div>
      )}

      <div className="capex-row" style={{ borderTop: "2px solid var(--border-strong)", marginTop: 8, paddingTop: 12 }}>
        <div className="capex-row-head">
          <span className="capex-cat-label" style={{ fontSize: 15 }}>{tr(lang, "Total EVCS CAPEX (full facility)", "Total CAPEX EVCS (fasilitas penuh)")}</span>
          <span className="capex-cat-total" style={{ fontSize: 16 }}>{fmt.rp(capex.total)}</span>
        </div>
      </div>
      <div className="capex-row">
        <div className="capex-row-head">
          <span className="capex-cat-label">{tr(lang, "In TCO scope (A+B, Electrical only)", "Dalam lingkup TCO (A+B, Elektrikal saja)")}</span>
          <span className="capex-cat-total">{fmt.rp(capex.tcoCapex)}</span>
        </div>
        <div className="capex-row-reason">
          {tr(lang,
            "Civil Works (C), Utility Upgrade (D), and Software (E), plus all EVCS OPEX, are managed and expended by Helio Sinar Energi — not counted in VKTR's TCO.",
            "Pekerjaan Sipil (C), Upgrade Utilitas (D), dan Perangkat Lunak (E), serta seluruh OPEX SPKLU, dikelola dan dibiayai oleh Helio Sinar Energi — tidak dihitung dalam TCO VKTR.")}
        </div>
      </div>
    </>
  );

  const fleetTooSmallForInfra = (s.fleetSize || 0) < 5 && !depotActive;

  return (
    <div>
      {fleetTooSmallForInfra && (
        <WarnHint label={tr(lang, "Small Fleet", "Armada Kecil")}
          note={tr(lang,
            `This comparison is loading the electrical CAPEX of a standalone charging depot (${fmt.rp(capex.tcoCapex)}) onto a fleet of just ${s.fleetSize || 0} vehicle(s) — nobody builds a whole depot for that few trucks. At this scale, infrastructure is normally shared across a much larger fleet, so this total will make the EV side look far worse than a real fleet-scale deployment would. Raise Fleet Size (Screen 4 → Fleet & Charging) to a realistic deployment size to see a representative comparison.`,
            `Perbandingan ini membebankan CAPEX elektrikal depot pengisian mandiri (${fmt.rp(capex.tcoCapex)}) ke armada hanya ${s.fleetSize || 0} kendaraan — tidak ada yang membangun depot penuh untuk sejumlah truk sekecil itu. Pada skala ini, infrastruktur biasanya dibagi ke armada yang jauh lebih besar, sehingga total ini akan membuat sisi EV terlihat jauh lebih buruk dibanding deployment skala armada yang nyata. Naikkan Jumlah Armada (Layar 4 → Armada & Pengisian) ke skala deployment yang realistis untuk melihat perbandingan yang representatif.`)} />
      )}
      {depotActive ? (
        <>
          <div className="audit-caption">
            {tr(lang, "Audit", "Audit")}
            <InfoHint note={tr(lang,
              "This is the Infrastructure section's financial audit page — one coherent CAPEX/OPEX total, currently sourced from Depot Design (Screen 4 → Depot Design tab). Scope is locked to Electrical/Charging CAPEX only, Rp0 OPEX — VKTR bears only the electrical equipment CAPEX (from Helio Sinar Energi); every other CAPEX category and all EVCS OPEX is Helio's, not a toggle.",
              "Ini adalah halaman audit keuangan bagian Infrastruktur — satu total CAPEX/OPEX yang koheren, saat ini bersumber dari Desain Depot (Layar 4 → tab Desain Depot). Lingkup terkunci ke CAPEX Elektrikal/Charging saja, OPEX Rp0 — VKTR hanya menanggung CAPEX peralatan elektrikal (dari Helio Sinar Energi); kategori CAPEX lain dan semua OPEX SPKLU adalah milik Helio, bukan pilihan yang bisa diubah.")} />
          </div>
          <DepotCapexBreakdown bom={s.depotBom} includeFlags={s.depotBomInclude} lang={lang} />
          <div style={{ marginTop: 14 }}>
            <CollapsibleSection title={tr(lang, "TCO Sizing Engine breakdown (reference only, not used in Results)", "Rincian Mesin Perhitungan TCO (referensi saja, tidak digunakan di Layar Hasil)")}>
              {sizingEngineBreakdown}
            </CollapsibleSection>
          </div>
        </>
      ) : (
        <>
          <div className="audit-caption">
            {tr(lang, "Audit", "Audit")}
            <InfoHint note={tr(lang,
              "This is the Infrastructure section's financial audit page — one coherent CAPEX/OPEX total, currently sourced from TCO's own Sizing Engine (Depot Design isn't active yet).",
              "Ini adalah halaman audit keuangan bagian Infrastruktur — satu total CAPEX/OPEX yang koheren, saat ini bersumber dari Mesin Perhitungan TCO sendiri (Desain Depot belum aktif).")} />
          </div>
          {sizingEngineBreakdown}
        </>
      )}

      <div className="budget-bar" style={{ marginTop: 14 }}>
        <div className="shift-row">
          <span className="shift-label">{depotActive ? tr(lang, "Depot Design CAPEX (in TCO)", "CAPEX Desain Depot (di TCO)") : tr(lang, "EVCS CAPEX", "CAPEX EVCS")}</span>
          <b>{fmt.rp(grandTotal)}</b>
        </div>
        <div className="shift-row">
          <span className="shift-label">{tr(lang, "Annual OPEX (in TCO scope)", "OPEX Tahunan (dalam lingkup TCO)")}</span>
          <b>{fmt.rp(depotActive ? s.depotBom.tcoOpex : 0)}</b>
        </div>
        <div className="shift-row">
          <span className="shift-label">{tr(lang, "Budget Cap", "Batas Anggaran")}</span>
          <b>{budgetCap != null ? fmt.rp(budgetCap) : tr(lang, "Not set", "Belum diatur")}</b>
        </div>
        {budgetCap != null && (
          <div className="shift-row">
            <span className="shift-label">{tr(lang, "Delta", "Selisih")}</span>
            <b style={{ color: grandTotal > budgetCap ? "var(--danger)" : "var(--c-accent)" }}>
              {grandTotal > budgetCap ? "+" : "−"}{fmt.rpShort(Math.abs(grandTotal - budgetCap))}
            </b>
          </div>
        )}
      </div>

      <BudgetAlert capexTotal={grandTotal} budgetCap={budgetCap}
        recommendations={depotActive ? [] : window.computeBudgetRecommendations(s, sizing, capex).map(r => summarizeBudgetRec(r, lang))} />
    </div>
  );
}

// ---------- Screen 4 shell ----------
// v1.8.3 (2026-07-18): the "Choose a preset" banner (PresetCard +
// PresetModal, top of screen) removed per Rija's explicit request --
// screen4_activePresetId/onSelectPreset's role (picking a curated
// INFRA_PROFILES.PRESETS scenario) is no longer reachable from this
// screen. Ecosystem is still settable directly on Screen 1, so this
// doesn't strand any state field; PresetCard/PresetModal (components.jsx)
// are unchanged/still used by Screen 5's own custom preset explorer, not
// deleted.
function Screen4({ s, set }) {
  const { lang } = useLang();
  const ecosystemId = s.ecosystemId || "others";
  const isOthers = ecosystemId === "others";

  useEffect(() => {
    if (s.approxFleetSize != null && s.fleetSize === DEFAULT_STATE.fleetSize && s.approxFleetSize !== DEFAULT_STATE.fleetSize) {
      set("fleetSize", s.approxFleetSize);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const veh = window.getEvVehicle(s);
  const activeTab = s.screen4_activeTab || 0;
  // v1.21: expertKey is looked up from INFRA_TABS[activeTab].expertTabKey
  // (explicit per-tab mapping) rather than derived by `activeTab+1` — that
  // arithmetic broke once Depot Design (no expert toggle) sat in the middle
  // of the tab order. expertKey is null for Depot Design.
  const expertKey = INFRA_TABS[activeTab] ? INFRA_TABS[activeTab].expertTabKey : null;
  const expertMode = isOthers ? true : !!(expertKey && s[expertKey]);

  const sizing = window.computeSizing(s);
  const capex = window.computeCapex(s, sizing);

  return (
    <>
      {(isOthers || !veh) && (
        <Card>
          {isOthers && (
            <span className="assumption-tag">
              {tr(lang, "Others ecosystem", "Ekosistem Lainnya")}
              <InfoHint note={tr(lang,
                "Others ecosystem — all parameters are open for custom input.",
                "Ekosistem Lainnya — semua parameter terbuka untuk input kustom.")} />
            </span>
          )}
          {!veh && (
            <div style={{ marginTop: isOthers ? 10 : 0 }}>
              <WarnHint label={tr(lang, "No EV Selected", "Tidak Ada EV")}
                note={tr(lang, "No EV selected in Vehicle Selection (Screen 2). Sizing and CAPEX tabs require at least one EV.", "Tidak ada EV yang dipilih di Pemilihan Kendaraan (Layar 2). Tab Sizing dan CAPEX membutuhkan minimal satu EV.")} />
            </div>
          )}
        </Card>
      )}

      <Card>
        <div className="infra-tab-bar">
          {INFRA_TABS.map((t, i) => (
            <div key={t.key} className={"infra-tab" + (activeTab === i ? " active" : "")}
              onClick={() => set("screen4_activeTab", i)}>
              <span>{t.icon}</span> {tr(lang, t.en, t.idLbl)}
              {!isOthers && t.expertTabKey && s[t.expertTabKey] && <span className="tab-expert-dot" />}
            </div>
          ))}
        </div>

        <div className="infra-tab-panel">
          <div style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 10, marginBottom: 12 }}>
            {!isOthers && expertKey && <ExpertToggle isOn={!!s[expertKey]} onToggle={v => set(expertKey, v)} />}
            <ResetScreenButton s={s} set={set} screenKey="screen4" />
          </div>

          {activeTab === 0 && <Tab1FleetCharging s={s} set={set} lang={lang} expertMode={expertMode} sizing={sizing} veh={veh} />}
          {activeTab === 1 && <Tab3GrowthAssets s={s} set={set} lang={lang} expertMode={expertMode} sizing={sizing} />}
          {activeTab === 2 && <Tab4SizingEngine s={s} set={set} lang={lang} expertMode={expertMode} sizing={sizing} />}
          {activeTab === 3 && <Tab6DepotDesign s={s} set={set} lang={lang} />}
          {activeTab === 4 && <Tab5CapexBreakdown s={s} set={set} lang={lang} expertMode={expertMode} sizing={sizing} capex={capex} />}
        </div>
      </Card>
    </>
  );
}

// ---------- Screen 5: Financials (+ inflation slider) ----------
// screen5_valueOverrides helper — keyed "finance.paramName"
function setFov(set, s, key, value) {
  const next = { ...(s.screen5_valueOverrides || {}) };
  if (value == null) delete next[key]; else next[key] = value;
  set("screen5_valueOverrides", next);
}

// Short label/detail summary of a budget recommendation, for the compact BudgetAlert preview
function summarizeBudgetRec(rec, lang) {
  switch (rec.id) {
    case "extend_window":
      return {
        id: rec.id,
        label: tr(lang, `Extend charging window by ${rec.addHours}h`, `Perpanjang jendela pengisian ${rec.addHours}j`),
        detail: tr(lang, `New CAPEX: ${fmt.rp(rec.newCapexTotal)}`, `CAPEX baru: ${fmt.rp(rec.newCapexTotal)}`),
      };
    case "downgrade_charger":
      return {
        id: rec.id,
        label: tr(lang, `Downgrade chargers to ${rec.newRating} kW`, `Turunkan charger ke ${rec.newRating} kW`),
        detail: tr(lang, `New CAPEX: ${fmt.rp(rec.newCapexTotal)}`, `CAPEX baru: ${fmt.rp(rec.newCapexTotal)}`),
      };
    case "reduce_redundancy":
      return {
        id: rec.id,
        label: tr(lang, `Reduce redundancy to ${rec.newRedundancy.toFixed(2)}×`, `Kurangi redundansi ke ${rec.newRedundancy.toFixed(2)}×`),
        detail: tr(lang, `New CAPEX: ${fmt.rp(rec.newCapexTotal)}`, `CAPEX baru: ${fmt.rp(rec.newCapexTotal)}`),
      };
    case "phase_deployment":
      return {
        id: rec.id,
        label: tr(lang, "Phase deployment over 2 stages", "Penerapan bertahap dalam 2 fase"),
        detail: tr(lang, `Phase 1: ${fmt.rp(rec.phase1.capexTotal)}`, `Fase 1: ${fmt.rp(rec.phase1.capexTotal)}`),
      };
    default:
      return {
        id: rec.id,
        label: tr(lang, "Budget infeasible with current scope", "Anggaran tidak mencukupi dengan cakupan saat ini"),
        detail: null,
      };
  }
}

const BUDGET_REC_NUMERALS = ["①", "②", "③", "④", "⑤"];

function BudgetRecommendation({ rec, idx, lang }) {
  const num = BUDGET_REC_NUMERALS[idx] || (idx + 1);
  if (rec.id === "extend_window") {
    return (
      <div className="capex-row">
        <div className="capex-row-head">
          <span className="capex-cat-label">{num} {tr(lang, "Extend charging window", "Perpanjang jendela pengisian daya")} (+{rec.addHours}{tr(lang, "h", "j")})</span>
          <span className="capex-cat-total">{fmt.rp(rec.newCapexTotal)}</span>
        </div>
        <div className="capex-row-reason">
          {tr(lang,
            `New window: ${rec.newWindow}h → required power drops to ~${fmt.num(Math.round(rec.newRequiredPowerKw))} kW (${fmt.num(rec.newChargerCount)} chargers)`,
            `Jendela baru: ${rec.newWindow}j → daya yang dibutuhkan turun ke ~${fmt.num(Math.round(rec.newRequiredPowerKw))} kW (${fmt.num(rec.newChargerCount)} charger)`)}
        </div>
      </div>
    );
  }
  if (rec.id === "downgrade_charger") {
    return (
      <div className="capex-row">
        <div className="capex-row-head">
          <span className="capex-cat-label">{num} {tr(lang, "Downgrade charger rating", "Turunkan daya charger")} ({rec.oldRating} kW → {rec.newRating} kW)</span>
          <span className="capex-cat-total">{fmt.rp(rec.newCapexTotal)}</span>
        </div>
        <div className="capex-row-reason">
          {tr(lang,
            `Trade-off: longer charge time required. Window must increase to ~${rec.suggestedWindowHours}h (${fmt.num(rec.newChargerCount)} chargers).`,
            `Trade-off: waktu pengisian lebih lama. Jendela harus bertambah menjadi ~${rec.suggestedWindowHours}j (${fmt.num(rec.newChargerCount)} charger).`)}
        </div>
      </div>
    );
  }
  if (rec.id === "reduce_redundancy") {
    return (
      <div className="capex-row">
        <div className="capex-row-head">
          <span className="capex-cat-label">{num} {tr(lang, "Reduce redundancy factor", "Kurangi faktor redundansi")} ({rec.oldRedundancy.toFixed(2)} → {rec.newRedundancy.toFixed(2)})</span>
          <span className="capex-cat-total">{fmt.rp(rec.newCapexTotal)}</span>
        </div>
        <div className="capex-row-reason">
          {tr(lang,
            "Risk: less spare charging capacity. Recommended only for low-criticality operations.",
            "Risiko: cadangan kapasitas pengisian berkurang. Hanya disarankan untuk operasi non-kritis.")}
        </div>
      </div>
    );
  }
  if (rec.id === "phase_deployment") {
    return (
      <div className="capex-row">
        <div className="capex-row-head">
          <span className="capex-cat-label">{num} {tr(lang, "Phase deployment", "Penerapan bertahap")}</span>
          <span className="capex-cat-total">{fmt.rp(rec.totalCapex)}</span>
        </div>
        <div className="capex-row-items">
          <div className="shift-row">
            <span className="shift-label">{tr(lang, "Phase 1 (Year 1)", "Fase 1 (Tahun 1)")}</span>
            <span>{fmt.num(rec.phase1.chargerCount)} {tr(lang, "chargers for", "charger untuk")} {fmt.num(rec.phase1.vehicles)} {tr(lang, "vehicles", "kendaraan")} — {fmt.rp(rec.phase1.capexTotal)}</span>
          </div>
          <div className="shift-row">
            <span className="shift-label">{tr(lang, "Phase 2 (Year 3)", "Fase 2 (Tahun 3)")}</span>
            <span>{fmt.num(rec.phase2.chargerCount)} {tr(lang, "chargers for", "charger untuk")} {fmt.num(rec.phase2.vehicles)} {tr(lang, "vehicles", "kendaraan")} — {fmt.rp(rec.phase2.capexTotal)}</span>
          </div>
        </div>
        <div className="capex-row-reason">
          {rec.phase1Fits
            ? tr(lang, "✓ Phase 1 fits within budget.", "✓ Fase 1 sesuai anggaran.")
            : tr(lang, "✗ Neither phase fits within budget.", "✗ Tidak ada fase yang sesuai anggaran.")}
        </div>
      </div>
    );
  }
  // infeasible
  return (
    <div className="capex-row not-applicable">
      <div className="capex-row-head">
        <span className="capex-cat-label">{num} {tr(lang, "Budget infeasible", "Anggaran tidak mencukupi")}</span>
      </div>
      <div className="capex-row-reason">
        {tr(lang,
          `Budget gap of ${fmt.rp(rec.gap)} cannot be resolved with current scope. Consider revising fleet size, charger spec, or budget.`,
          `Selisih anggaran sebesar ${fmt.rp(rec.gap)} tidak dapat diselesaikan dengan cakupan saat ini. Pertimbangkan untuk merevisi jumlah armada, spesifikasi charger, atau anggaran.`)}
      </div>
    </div>
  );
}

const SCREEN5_TABS = [
  { icon: "💰", en: "Inputs", id: "Input" },
  { icon: "🧮", en: "Audit", id: "Audit" },
];

// ---------- Screen 5 Tab 2: Audit — financing cost breakdown & WACC/inflation effect ----------
function Screen5AuditPanel({ s, lang }) {
  const vA = window.findVeh(s.vehA), vB = window.findVeh(s.vehB);
  if (!vA || !vB) {
    return (
      <WarnHint label={tr(lang, "Select Vehicles", "Pilih Kendaraan")}
        note={tr(lang, "Select both vehicles in Vehicle Selection (Screen 2) to see the financial audit.", "Pilih kedua kendaraan di Pemilihan Kendaraan (Layar 2) untuk melihat audit keuangan.")} />
    );
  }
  const R = window.computeTCO(s);
  const priceA = s.priceA ?? vA.price, priceB = s.priceB ?? vB.price;
  const isLoanA = s.paymentA === "loan";
  const isLoanB = s.paymentB === "loan";
  const principalA = priceA * (s.fleetSize || 0) * (1 - (s.downPayment || 0) / 100);
  const principalB = priceB * (s.fleetSize || 0) * (1 - (s.downPayment || 0) / 100);
  const isAmortizing = s.loanInterestMethod === "amortizing";
  const finPerYearA = isLoanA && !isAmortizing ? principalA * ((s.interest || 0) / 100) : null;
  const finPerYearB = isLoanB && !isAmortizing ? principalB * ((s.interest || 0) / 100) : null;
  const isSohResidual = (s.residualValueMethod ?? "soh") === "soh";
  return (
    <div>
      <div className="audit-caption">
        {tr(lang, "Audit", "Audit")}
        <InfoHint note={tr(lang,
          "Financial audit for this section — the financing cost breakdown actually applied per vehicle, plus how WACC and inflation feed into the NPV/IRR and OPEX projections on the Results screen.",
          "Audit keuangan untuk bagian ini — rincian biaya pembiayaan yang benar-benar diterapkan per kendaraan, beserta bagaimana WACC dan inflasi masuk ke proyeksi NPV/IRR dan OPEX di Layar Hasil.")} />
      </div>
      <table className="sbs-table" style={{ marginTop: 14 }}>
        <thead>
          <tr><th>{tr(lang, "Item", "Item")}</th><th>{vA.name}</th><th>{vB.name}</th></tr>
        </thead>
        <tbody>
          <tr><td className="lbl">{tr(lang, "Payment Method", "Metode Pembayaran")}</td>
            <td>{isLoanA ? tr(lang, "Loan", "Kredit") : tr(lang, "Cash", "Tunai")}</td>
            <td>{isLoanB ? tr(lang, "Loan", "Kredit") : tr(lang, "Cash", "Tunai")}</td></tr>
          {(isLoanA || isLoanB) && (<>
            <tr><td className="lbl">{tr(lang, "Interest Method", "Metode Bunga")}</td>
              <td colSpan={2}>{isAmortizing
                ? tr(lang, "Amortizing — declining-balance annuity (window.amortizingInterestByYear)", "Amortizing — anuitas saldo menurun (window.amortizingInterestByYear)")
                : tr(lang, "Flat — interest on original principal every year", "Flat — bunga atas pokok awal setiap tahun")}</td></tr>
            <tr><td className="lbl">{tr(lang, "Financed Principal", "Pokok Dibiayai")}</td>
              <td className="num">{isLoanA ? fmt.rp(principalA) : "—"}</td>
              <td className="num">{isLoanB ? fmt.rp(principalB) : "—"}</td></tr>
            {!isAmortizing && (
              <tr><td className="lbl">{tr(lang, "Annual Financing Cost", "Biaya Pembiayaan/Thn")}</td>
                <td className="num">{isLoanA ? fmt.rp(finPerYearA) : "—"}</td>
                <td className="num">{isLoanB ? fmt.rp(finPerYearB) : "—"}</td></tr>
            )}
            {isAmortizing && (
              <tr><td className="lbl">{tr(lang, "First-Year Interest (declines thereafter)", "Bunga Tahun Pertama (menurun setelahnya)")}</td>
                <td className="num">{isLoanA ? fmt.rp(R.A.finCostAnnual[0] || 0) : "—"}</td>
                <td className="num">{isLoanB ? fmt.rp(R.B.finCostAnnual[0] || 0) : "—"}</td></tr>
            )}
          </>)}
          <tr className="total"><td className="lbl">{tr(lang, "Total Financing Cost", "Total Biaya Pembiayaan")}</td><td className="num">{fmt.rp(R.rows[1].a)}</td><td className="num">{fmt.rp(R.rows[1].b)}</td></tr>
          <tr><td className="lbl">{tr(lang, "Residual Value Method", "Metode Nilai Sisa")}</td>
            <td colSpan={2}>{isSohResidual
              ? tr(lang, "SOH-linked (EV) — battery-weighted, tied to Battery Degradation Rate below; DJKN schedule (ICE)", "Terkait SOH (EV) — berbobot baterai, terkait Tingkat Degradasi Baterai di bawah; skedul DJKN (ICE)")
              : tr(lang, "Fixed depreciation schedule (both powertrains)", "Skedul depresiasi tetap (kedua jenis penggerak)")}</td></tr>
          <tr><td className="lbl">{tr(lang, "Vehicle A Acquisition Basis", "Basis Akuisisi Kendaraan A")}</td>
            <td colSpan={2}>{R.A.isSunkAsset
              ? tr(lang, "Sunk — existing fleet vehicle, no new CAPEX/financing/infra counted", "Sunk — kendaraan armada eksisting, tidak ada CAPEX/pembiayaan/infra baru yang dihitung")
              : tr(lang, "New acquisition — full CAPEX/financing/infra counted", "Akuisisi baru — CAPEX/pembiayaan/infra penuh dihitung")}</td></tr>
          <tr><td className="lbl">{tr(lang, "Emission Factors Used", "Faktor Emisi Digunakan")}</td>
            <td colSpan={2}>{tr(lang,
              `Diesel: ${fmt.num(window.CO2.diesel_kg_per_liter)} kgCO2/L (well-to-wheel) · Grid: ${fmt.num(window.CO2.grid_kg_per_kwh)} kgCO2/kWh (Indonesia national average). Embodied battery-manufacturing CO2: ${s.includeEmbodiedCo2 ? `included, ${fmt.num(s.evBatteryMfgCo2PerKwh ?? 74)} kgCO2/kWh` : "not included (off by default)"}.`,
              `Solar: ${fmt.num(window.CO2.diesel_kg_per_liter)} kgCO2/L (well-to-wheel) · Grid: ${fmt.num(window.CO2.grid_kg_per_kwh)} kgCO2/kWh (rata-rata nasional Indonesia). CO2 manufaktur baterai tertanam: ${s.includeEmbodiedCo2 ? `disertakan, ${fmt.num(s.evBatteryMfgCo2PerKwh ?? 74)} kgCO2/kWh` : "tidak disertakan (nonaktif secara default)"}.`)}</td></tr>
        </tbody>
      </table>
      <div className="spec-grid" style={{ marginTop: 14 }}>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "WACC (discount rate)", "WACC (tingkat diskonto)")}</div>
          <div className="sv">{s.wacc}%</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Annual Cost Inflation", "Inflasi Biaya Tahunan")}</div>
          <div className="sv">{s.inflation ?? 5}%</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Resulting NPV (A vs B)", "NPV Hasil (A vs B)")}</div>
          <div className="sv">{R.npv != null ? fmt.rp(R.npv) : "—"}</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Resulting IRR", "IRR Hasil")}</div>
          <div className="sv">{R.irr != null ? `${R.irr.toFixed(1)}%` : "—"}</div>
        </div>
        <div className="spec-cell">
          <div className="sk">{tr(lang, "Residual Schedule Used", "Skema Nilai Sisa Digunakan")}</div>
          <div className="sv" style={{ fontSize: 13 }}>
            A: {vA.powertrain === "EV" ? tr(lang, "EV (assumption)", "EV (asumsi)") : tr(lang, "ICE (DJKN)", "ICE (DJKN)")}
            {" · "}
            B: {vB.powertrain === "EV" ? tr(lang, "EV (assumption)", "EV (asumsi)") : tr(lang, "ICE (DJKN)", "ICE (DJKN)")}
          </div>
        </div>
        {(vA.powertrain === "EV" || vB.powertrain === "EV") && (
          <div className="spec-cell">
            <div className="sk">{tr(lang, "Battery Replacement Cost", "Biaya Penggantian Baterai")}</div>
            <div className="sv" style={{ fontSize: 13 }}>
              {s.modelBatteryReplacement
                ? `A: ${fmt.rp(R.rows.find(r => r.en === "Battery Replacement Cost (EV)").a)} · B: ${fmt.rp(R.rows.find(r => r.en === "Battery Replacement Cost (EV)").b)}`
                : tr(lang, "Not modeled (off by default)", "Tidak dimodelkan (nonaktif secara default)")}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function Screen5({ s, set }) {
  const { lang } = useLang();
  const [advOpen, setAdvOpen] = useState(false);
  // Which vehicle's expense-bucket model to explore (v1.7.7) — ephemeral
  // UI-only preference, not persisted to `s`. Defaults to B, same as the
  // old Commercial Scheme Ladder's default.
  const [bucketVehKey, setBucketVehKey] = useState("B");
  const activeTab = s.screen5_activeTab || 0;
  const isLoanA = s.paymentA === "loan";
  const isLoanB = s.paymentB === "loan";
  const isLoan = isLoanA || isLoanB;
  const inflPct = s.inflation ?? 5;
  const vA5 = window.findVeh(s.vehA), vB5 = window.findVeh(s.vehB);
  const anyEv5 = vA5?.powertrain === "EV" || vB5?.powertrain === "EV";

  const ecosystemId = s.ecosystemId || "others";
  const effectiveEcosystemId = s.costModelFlat ? "others" : ecosystemId;
  const finPreset = window.INFRA_PROFILES.GET_FINANCIAL_PRESET(effectiveEcosystemId);
  const fov = s.screen5_valueOverrides || {};
  const fdef = (key) => finPreset[key].value;
  const fget = (key) => fov["finance." + key] ?? fdef(key);
  const fsetPct = (key, v) => { const n = Number(v.replace(/\D/g, "")); setFov(set, s, "finance." + key, isNaN(n) ? null : n / 100); };

  const finPctField = (key, en, idLbl) => {
    const cur = fov["finance." + key];
    const curPct = cur != null ? Math.round(cur * 100) : null;
    const defPct = Math.round(fdef(key) * 100);
    return (
      <Field en={en} id={idLbl}>
        <AffixInput value={fmt.num(curPct ?? defPct)} suffix="%" onChange={v => fsetPct(key, v)} />
        <ValueFlag defaultValue={defPct} currentValue={curPct} onReset={() => setFov(set, s, "finance." + key, null)} />
      </Field>
    );
  };

  const finYearsField = (key, en, idLbl) => {
    const cur = fov["finance." + key];
    const def = fdef(key);
    return (
      <Field en={en} id={idLbl}>
        <AffixInput value={fmt.num(cur ?? def)} suffix={tr(lang, "yrs", "thn")}
          onChange={v => { const n = Number(v.replace(/\D/g, "")); setFov(set, s, "finance." + key, isNaN(n) ? null : n); }} />
        <ValueFlag defaultValue={def} currentValue={cur} onReset={() => setFov(set, s, "finance." + key, null)} />
      </Field>
    );
  };

  const sizing = window.computeSizing(s);
  const capex = window.computeCapex(s, sizing);
  const recommendations = window.computeBudgetRecommendations(s, sizing, capex);

  const cashRatio = Math.round(fget("cashRatio") * 100);
  const loanRatio = Math.round(fget("loanRatio") * 100);
  const leaseRatio = Math.round(fget("leaseRatio") * 100);
  const ratioSum = cashRatio + loanRatio + leaseRatio;

  return (
    <>
      <Card>
        <div className="infra-tab-bar">
          {SCREEN5_TABS.map((t, i) => (
            <div key={i} className={"infra-tab" + (activeTab === i ? " active" : "")}
              onClick={() => set("screen5_activeTab", i)}>
              <span>{t.icon}</span> {tr(lang, t.en, t.id)}
            </div>
          ))}
          <div style={{ marginLeft: "auto", alignSelf: "center" }}>
            <ResetScreenButton s={s} set={set} screenKey="screen5" />
          </div>
        </div>
      </Card>
      {activeTab === 1 && <Screen5AuditPanel s={s} lang={lang} />}
      {activeTab === 0 && <>
      <Card title="Project Budget" idSub="Anggaran Proyek">
        <div className="grid-2">
          <Field en="Infrastructure Budget Cap (EVCS only)" id="Batas Anggaran Infrastruktur (khusus EVCS)" opt>
            <AffixInput value={s.infraBudgetCap != null ? fmt.num(s.infraBudgetCap) : ""} prefix="Rp"
              onChange={v => { const c = v.replace(/\D/g, ""); set("infraBudgetCap", c === "" ? null : Number(c)); }} />
          </Field>
          <Field en="Total Project Budget Cap (vehicle + infra)" id="Batas Anggaran Total Proyek (kendaraan + infra)" opt>
            <AffixInput value={s.totalProjectBudgetCap != null ? fmt.num(s.totalProjectBudgetCap) : ""} prefix="Rp"
              onChange={v => { const c = v.replace(/\D/g, ""); set("totalProjectBudgetCap", c === "" ? null : Number(c)); }} />
          </Field>
        </div>
      </Card>

      {recommendations.length > 0 && (
        <Card title="Budget Optimization" idSub="Optimasi Anggaran">
          <WarnHint label={tr(lang, "Budget Exceeded", "Anggaran Terlampaui")}
            note={tr(lang,
              `EVCS CAPEX (${fmt.rp(capex.tcoCapex)}) exceeds infrastructure budget (${fmt.rp(s.infraBudgetCap)}) by ${fmt.rp(capex.tcoCapex - s.infraBudgetCap)}.`,
              `CAPEX EVCS (${fmt.rp(capex.tcoCapex)}) melebihi anggaran infrastruktur (${fmt.rp(s.infraBudgetCap)}) sebesar ${fmt.rp(capex.tcoCapex - s.infraBudgetCap)}.`)} />
          <div style={{ marginTop: 12 }}>
            <div style={{ fontWeight: 700, color: "var(--c-primary)", marginBottom: 8 }}>
              {tr(lang, "Optimization Options", "Opsi Optimasi")}
            </div>
            {recommendations.map((rec, i) => (
              <BudgetRecommendation key={rec.id} rec={rec} idx={i} lang={lang} />
            ))}
          </div>
        </Card>
      )}

      {(() => {
        const paymentOptions = [
          { value: "cash", icon: "💰", en: "Cash", id: "Tunai" },
          { value: "loan", icon: "🏦", en: "Loan", id: "Kredit" },
        ];
        const paymentField = (vehLabel, vehName, key) => (
          <Field en={`Vehicle ${vehLabel} (${vehName})`} id={`Kendaraan ${vehLabel} (${vehName})`}>
            <div className="option-card-grid" style={{ gridTemplateColumns: "repeat(2, 1fr)" }}>
              {paymentOptions.map(o => (
                <div key={o.value} className={"option-card" + (s[key] === o.value ? " active" : "")}
                  onClick={() => set(key, o.value)}>
                  <div className="oc-icon">{o.icon}</div>
                  <div className="oc-label">{tr(lang, o.en, o.id)}</div>
                </div>
              ))}
            </div>
          </Field>
        );
        return (
          <Card title="Payment Method" idSub="Metode Pembayaran"
            head={<InfoHint note={tr(lang,
              "Simple cash/loan financing for each vehicle's own purchase — unrelated to who bears which expense bucket, see Expense Bucket Toggle below.",
              "Pembiayaan tunai/kredit sederhana untuk pembelian masing-masing kendaraan — tidak terkait dengan siapa menanggung kelompok biaya mana, lihat Toggle Kelompok Biaya di bawah.")} />}>
            <div className="grid-2">
              {paymentField("A", vA5?.name ?? "A", "paymentA")}
              {paymentField("B", vB5?.name ?? "B", "paymentB")}
            </div>
            <div className="collapse-body" style={{ maxHeight: isLoan ? 2000 : 0, transition: "max-height .3s ease", marginTop: isLoan ? 18 : 0 }}>
              <div className="grid-3">
                <Field en="Interest Rate" id="Suku Bunga">
                  <AffixInput value={s.interest} suffix="% flat/thn" onChange={v => set("interest", Number(v.replace(/[^\d.]/g, "")) || 0)} />
                </Field>
                {isLoan && (
                  <Field en="Down Payment" id="Uang Muka">
                    <AffixInput value={s.downPayment} suffix="%" onChange={v => set("downPayment", Number(v.replace(/[^\d.]/g, "")) || 0)} />
                  </Field>
                )}
                {isLoan && (
                  <Field en="Loan Duration" id="Tenor">
                    <AffixInput value={s.tenor} suffix="tahun" onChange={v => set("tenor", Number(v.replace(/\D/g, "")) || 0)} />
                  </Field>
                )}
              </div>
              {isLoan && (
                <div style={{ marginTop: 14 }}>
                  <Field en="Interest Method" id="Metode Bunga"
                    helpEn="Flat (default) preserves every already-validated comparison — interest is charged on the original principal every year. Amortizing uses a standard declining-balance annuity, which totals less interest for the same nominal rate as the loan balance pays down."
                    help="Flat (default) menjaga semua perbandingan yang sudah divalidasi — bunga dikenakan pada pokok awal setiap tahun. Amortizing memakai anuitas saldo menurun standar, total bunga lebih rendah pada suku bunga nominal yang sama seiring saldo berkurang.">
                    <PillToggle value={s.loanInterestMethod ?? "flat"} onChange={v => set("loanInterestMethod", v)}
                      left={{ value: "flat", label: tr(lang, "Flat", "Flat") }}
                      right={{ value: "amortizing", label: tr(lang, "Amortizing", "Amortizing") }} />
                  </Field>
                </div>
              )}
            </div>
          </Card>
        );
      })()}

      {vA5 && vB5 && (() => {
        const bm = window.computeExpenseBuckets(s, bucketVehKey);
        if (!bm) return null;
        const state = { ...window.DEFAULT_STATE.expenseBucketState, ...(s.expenseBucketState || {}) };
        const markup = { ...window.DEFAULT_STATE.expenseBucketMarkupPct, ...(s.expenseBucketMarkupPct || {}) };
        const setBucketState = (k, v) => set("expenseBucketState", { ...state, [k]: v });
        const setBucketMarkup = (k, v) => set("expenseBucketMarkupPct", { ...markup, [k]: v });
        const activePreset = window.EXPENSE_BUCKET_PRESETS.find(p =>
          window.EXPENSE_BUCKET_KEYS.every(k => (state[k] || "customer") === (p.state[k] || "customer")));
        return (
          <Card title="Expense Bucket Toggle" idSub="Toggle Kelompok Biaya"
            head={<span className="assumption-tag" style={{ textTransform: "none" }}>
              {tr(lang, "Design proposal", "Proposal Desain")}
              <InfoHint note={tr(lang,
                `Who bears each of ${bm.veh.name}'s cost buckets — customer directly, or VKTR (marked up, if a rate is set). Replaces the old 4-scheme Commercial Scheme Ladder. WARRANTY's cost is a placeholder pending real VKTR service/warranty-claims data — see CALCULATION_ENGINE.md.`,
                `Siapa yang menanggung setiap kelompok biaya ${bm.veh.name} — pelanggan langsung, atau VKTR (dengan markup, jika tarif diatur). Menggantikan Perbandingan Skema Komersial 4-skema yang lama. Biaya WARRANTY adalah placeholder menunggu data klaim garansi/servis VKTR riil — lihat CALCULATION_ENGINE.md.`)} />
            </span>}>
            <div style={{ marginBottom: 12 }}>
              <PillToggle value={bucketVehKey} onChange={setBucketVehKey}
                left={{ value: "A", label: `A · ${vA5.name}` }}
                right={{ value: "B", label: `B · ${vB5.name}` }} />
            </div>

            <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 14 }}>
              {window.EXPENSE_BUCKET_PRESETS.map(p => (
                <button key={p.id} type="button"
                  className={"chip" + (activePreset?.id === p.id ? " chip-active" : "")}
                  title={tr(lang, p.desc.en, p.desc.id)}
                  onClick={() => set("expenseBucketState", { ...p.state })}>
                  {tr(lang, p.en, p.idLbl)}
                </button>
              ))}
            </div>

            <div className="grid-2" style={{ marginBottom: 14 }}>
              <Field en="Subscription Term" id="Jangka Waktu Sewa"
                helpEn="Distinct from Horizon above -- VKTR-borne buckets are priced to recover their full cost within this many years, then the subscription renews at the same rate for the rest of the Horizon (pure margin for VKTR from then on)."
                help="Terpisah dari Horizon di atas -- kelompok yang ditanggung VKTR diberi harga untuk menutup seluruh biayanya dalam jumlah tahun ini, lalu berlangganan diperpanjang dengan tarif yang sama untuk sisa Horizon (murni margin VKTR sejak saat itu).">
                <AffixInput value={fmt.num(s.subscriptionTermYears ?? 5)} suffix={tr(lang, "yrs", "thn")}
                  onChange={v => set("subscriptionTermYears", Number(v.replace(/\D/g, "")) || 1)} />
              </Field>
              {(s.subscriptionTermYears ?? 5) > (s.horizon || 5) && (
                <div style={{ display: "flex", alignItems: "flex-end", paddingBottom: 10 }}>
                  <WarnHint label={tr(lang, "Term > Horizon", "Jangka > Horizon")}
                    note={tr(lang,
                      "Subscription Term should be less than or equal to Horizon. With Term longer than Horizon, VKTR hasn't broken even on its cost basis within this comparison window -- still a valid, well-defined result, just an incomplete recovery, not an error. Raise Horizon or lower Term above to fix.",
                      "Jangka Waktu Sewa sebaiknya lebih pendek atau sama dengan Horizon. Dengan Jangka lebih panjang dari Horizon, VKTR belum balik modal dalam jendela perbandingan ini -- tetap hasil yang valid dan jelas, hanya belum tertutup penuh, bukan kesalahan. Naikkan Horizon atau turunkan Jangka di atas untuk memperbaikinya.")} />
                </div>
              )}
            </div>

            <table className="sbs-table" style={{ width: "100%" }}>
              <thead>
                <tr>
                  <th>{tr(lang, "Bucket", "Kelompok")}</th>
                  <th style={{ textAlign: "right" }}>{tr(lang, "Lifetime (fleet)", "Seumur Hidup (armada)")}</th>
                  <th>{tr(lang, "Borne by", "Ditanggung")}</th>
                  <th style={{ textAlign: "right" }}>{tr(lang, "Markup", "Markup")}</th>
                </tr>
              </thead>
              <tbody>
                {window.EXPENSE_BUCKET_KEYS.map(k => {
                  const b = bm.buckets[k];
                  const label = window.EXPENSE_BUCKET_LABELS[k];
                  const note = window.EXPENSE_BUCKET_NOTES?.[k];
                  return (
                    <tr key={k}>
                      <td>{tr(lang, label.en, label.id)}{note && <InfoHint note={tr(lang, note.en, note.id)} />}</td>
                      <td style={{ textAlign: "right" }}>{fmt.rp(b.raw)}</td>
                      <td>
                        <PillToggle value={b.bearer} onChange={v => setBucketState(k, v)}
                          left={{ value: "customer", label: tr(lang, "Customer", "Pelanggan") }}
                          right={{ value: "vktr", label: "VKTR" }} />
                      </td>
                      <td style={{ textAlign: "right" }}>
                        {b.bearer === "vktr"
                          ? <AffixInput value={fmt.num(b.markupPct)} suffix="%"
                              onChange={v => setBucketMarkup(k, Number(v.replace(/[^\d.]/g, "")) || 0)} />
                          : <span style={{ color: "var(--text-muted)" }}>—</span>}
                      </td>
                    </tr>
                  );
                })}
                <tr className="total">
                  <td className="lbl">{tr(lang, "Total (pre-markup)", "Total (sebelum markup)")}</td>
                  <td className="num">{fmt.rp(bm.total)}</td>
                  <td colSpan={2}></td>
                </tr>
              </tbody>
            </table>

            <div className="grid-2" style={{ marginTop: 14 }}>
              <Field en="Unit (Capital) Resale Relief" id="Keringanan Jual Kembali Unit (Modal)"
                helpEn="Off by default -- Unit (Capital) shows the fresh buying price (+ financing) only, matching Vehicle Selection at a glance. Turn on to net an estimated resale/residual value as an optional relief."
                help="Nonaktif secara default -- Unit (Modal) menampilkan hanya harga beli baru (+ pembiayaan), sesuai dengan Pemilihan Kendaraan sekilas. Aktifkan untuk mengurangkan estimasi nilai jual kembali/sisa sebagai keringanan opsional.">
                <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", height: 38 }}>
                  <input type="checkbox" checked={!!s.applyResaleReliefInUnit}
                    onChange={e => set("applyResaleReliefInUnit", e.target.checked)} />
                  {tr(lang, "Apply resale relief", "Terapkan keringanan jual kembali")}
                </label>
              </Field>
              {s.applyResaleReliefInUnit && (
                <Field en={`Estimated Resale Value — ${bm.veh.name}`} id={`Estimasi Nilai Jual Kembali — ${bm.veh.name}`} opt
                  helpEn="Leave blank to use the auto-computed residual value (shown as the placeholder) as the relief amount."
                  help="Biarkan kosong untuk memakai nilai sisa terhitung otomatis (ditampilkan sebagai placeholder) sebagai jumlah keringanan.">
                  <AffixInput prefix="Rp"
                    value={s[`resaleValueOverride${bucketVehKey}`] != null ? fmt.num(s[`resaleValueOverride${bucketVehKey}`]) : ""}
                    placeholder={fmt.num(Math.round(bm.RV))}
                    onChange={v => { const c = v.replace(/\D/g, ""); set(`resaleValueOverride${bucketVehKey}`, c === "" ? null : Number(c)); }} />
                </Field>
              )}
            </div>

            <div className="spec-grid" style={{ marginTop: 14 }}>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "Customer Bears (direct)", "Pelanggan Menanggung (langsung)")}</div>
                <div className="sv">{fmt.rp(bm.customerDirect)}</div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "VKTR Bears (raw cost)", "VKTR Menanggung (biaya riil)")}</div>
                <div className="sv">{fmt.rp(bm.vktrBorneRaw)}</div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "Customer Total Payment", "Total Bayar Pelanggan")}</div>
                <div className="sv">{fmt.rp(bm.customerTotalPayment)}</div>
              </div>
              <div className="spec-cell">
                <div className="sk">{tr(lang, "Subscription / yr", "Sewa / thn")}</div>
                <div className="sv">{fmt.rp(bm.subscriptionAnnual)}</div>
              </div>
              <div className="spec-cell">
                <div className="sk">Rp/km ({tr(lang, "customer total", "total pelanggan")})</div>
                <div className="sv">{fmt.num(Math.round(bm.rates.customerTotalPayment.km))}</div>
              </div>
            </div>
            {bm.subscriptionAnnual > 0 && (s.horizon || 5) > bm.subscriptionTermYears && (
              <div className="note" style={{ marginTop: 8 }}>
                {tr(lang,
                  `Renews after year ${bm.subscriptionTermYears} at the same ${fmt.rp(bm.subscriptionAnnual)}/yr for the remaining ${(s.horizon || 5) - bm.subscriptionTermYears} year(s) of the horizon — VKTR has recovered its cost basis by then, so those years are margin.`,
                  `Diperpanjang setelah tahun ke-${bm.subscriptionTermYears} dengan tarif yang sama ${fmt.rp(bm.subscriptionAnnual)}/thn untuk ${(s.horizon || 5) - bm.subscriptionTermYears} tahun sisa horizon — VKTR sudah balik modal saat itu, jadi tahun-tahun tersebut adalah margin.`)}
              </div>
            )}
            <div className="note" style={{ marginTop: 12 }}>
              {tr(lang,
                `Rp/ton-km (customer total): ${fmt.num(Math.round(bm.rates.customerTotalPayment.tonKm))} · Rp/hour (customer total, avg. ${s.avgOperatingSpeedKmh || 25} km/h): ${fmt.num(Math.round(bm.rates.customerTotalPayment.hour))}.`,
                `Rp/ton-km (total pelanggan): ${fmt.num(Math.round(bm.rates.customerTotalPayment.tonKm))} · Rp/jam (total pelanggan, rata-rata ${s.avgOperatingSpeedKmh || 25} km/j): ${fmt.num(Math.round(bm.rates.customerTotalPayment.hour))}.`)}
            </div>
          </Card>
        );
      })()}

      <Card title="Energy Prices" idSub="Harga Energi">
        <div className="grid-2">
          <Field en="Diesel Price" id="Harga Solar">
            <AffixInput value={fmt.num(s.diesel)} prefix="Rp" suffix="/liter"
              onChange={v => set("diesel", Number(v.replace(/\D/g, "")) || 0)} />
          </Field>
          <Field en="Electricity Tariff" id="Tarif Listrik"
            help="Default Tarif I-4 (≥30.000 kVA) Rp 1.000/kWh. Subsidi: Rp 731/kWh · SPKLU: Rp 2.500/kWh"
            helpEn="Default Tariff I-4 (≥30,000 kVA) Rp 1,000/kWh. Subsidised: Rp 731/kWh · SPKLU: Rp 2,500/kWh">
            <AffixInput value={fmt.num(s.electricity)} prefix="Rp" suffix="/kWh"
              onChange={v => set("electricity", Number(v.replace(/\D/g, "")) || 0)} />
          </Field>
        </div>
      </Card>

      <Card title="Cost Inflation" idSub="Inflasi Biaya Berulang">
        <Field
          en="Annual Cost Inflation"
          id="Kenaikan Biaya Tahunan"
          helpEn="Compound annual rate applied to energy and maintenance costs. Year 1 = no inflation; Year 2 = ×(1+rate); etc. Set 0% for flat-rate output identical to v1.1 baseline."
          help="Kenaikan biaya majemuk per tahun, diterapkan pada biaya energi dan perawatan. Tahun 1 = tanpa inflasi. Set 0% untuk output flat setara baseline v1.1.">
          <div className="slider-wrap">
            <input
              type="range" className="range" min={0} max={15} step={0.5}
              value={inflPct}
              style={{
                background: `linear-gradient(to right, var(--c-accent) 0%, var(--c-accent) ${(inflPct / 15) * 100}%, var(--border) ${(inflPct / 15) * 100}%, var(--border) 100%)`
              }}
              onChange={e => set("inflation", Number(e.target.value))}
            />
            <div className="affix" style={{ width: 110, flex: "none" }}>
              <input
                value={inflPct}
                inputMode="decimal"
                style={{ textAlign: "center" }}
                onChange={e => {
                  let n = parseFloat(e.target.value);
                  if (isNaN(n)) n = 0;
                  set("inflation", Math.max(0, Math.min(15, n)));
                }}
              />
              <span className="fix suf">% / thn</span>
            </div>
          </div>
          <div style={{ fontSize: 12, color: "var(--text-muted)", marginTop: 4 }}>
            {inflPct === 0
              ? tr(lang, "0% — flat-rate output (no cost escalation)", "0% — biaya flat tanpa eskalasi")
              : tr(lang, `${inflPct}% compound per year (default = 5%)`, `${inflPct}% majemuk per tahun (default = 5%)`)}
          </div>
        </Field>
      </Card>

      <Card title="Warranty Reserve" idSub="Cadangan Garansi"
        head={<span className="assumption-tag">
          {tr(lang, "Placeholder rate — editable", "Tarif placeholder — dapat diubah")}
          <InfoHint note={tr(lang,
            "Models unscheduled/out-of-cycle repairs not covered by the Maintenance Breakdown schedule. Applied as a flat %/yr of each vehicle's own OTR (on-the-road price x fleet size), for the full Horizon -- a placeholder pending real VKTR service/warranty-claims data, not a validated actuarial rate. EV battery replacement cost (if enabled below) is added on top of this, not replaced by it.",
            "Memodelkan perbaikan tak terjadwal/di luar siklus yang tidak tercakup jadwal Rincian Perawatan. Diterapkan sebagai %/thn flat dari OTR masing-masing kendaraan (harga OTR x jumlah armada), untuk seluruh Horizon -- tarif placeholder menunggu data klaim garansi/servis VKTR riil, bukan tarif aktuaria tervalidasi. Biaya penggantian baterai EV (jika diaktifkan di bawah) ditambahkan di atas ini, bukan menggantikannya.")} />
        </span>}>
        <div className="grid-3">
          <Field en="Unscheduled Repair Reserve" id="Cadangan Perbaikan Tak Terjadwal"
            helpEn="Default 1.5%/yr of OTR -- a placeholder, not a validated rate. Feeds the WARRANTY row of the Expense Bucket Toggle table above."
            help="Default 1,5%/thn dari OTR -- placeholder, bukan tarif tervalidasi. Menjadi dasar baris WARRANTY pada tabel Toggle Kelompok Biaya di atas.">
            <AffixInput value={fmt.num(s.unscheduledRepairReservePctOfOtr ?? 1.5)} suffix="%/thn"
              onChange={v => { const c = v.replace(/[^\d.]/g, ""); set("unscheduledRepairReservePctOfOtr", c === "" ? 1.5 : Number(c)); }} />
          </Field>
        </div>
      </Card>

      <Card title="Insurance Cost" idSub="Biaya Asuransi"
        head={<span className="assumption-tag">
          {tr(lang, "Optional — off by default", "Opsional — nonaktif secara default")}
          <InfoHint note={tr(lang,
            "Off by default (Rp 0) so it doesn't change any comparison you've already validated. A % of each vehicle's own price, so it naturally differs between A and B. Labor cost (drivers, technicians, operational staff) is out of scope for this platform per VKTR team decision — see Results → Assumptions & Limitations — and isn't modeled here; the only labor line the platform still tracks is depot security staffing inside the Depot Design tool.",
            "Nonaktif secara default (Rp 0) sehingga tidak mengubah perbandingan yang sudah Anda validasi. Berupa % dari harga masing-masing kendaraan, sehingga otomatis berbeda antara A dan B. Biaya tenaga kerja (pengemudi, teknisi, staf operasional) di luar lingkup platform ini sesuai keputusan tim VKTR — lihat Hasil → Asumsi & Keterbatasan — dan tidak dimodelkan di sini; satu-satunya baris tenaga kerja yang masih dilacak platform adalah staf keamanan depot di dalam alat Desain Depot.")} />
        </span>}>
        <div className="grid-3">
          <Field en="Insurance Rate" id="Tarif Asuransi" opt
            helpEn="% of unit price per year, e.g. 2% typical Indonesian commercial-vehicle comprehensive insurance."
            help="% dari harga unit per tahun, mis. 2% tipikal asuransi comprehensive kendaraan komersial di Indonesia.">
            <AffixInput value={s.insuranceRatePct != null ? fmt.num(s.insuranceRatePct) : ""} suffix="%/thn"
              onChange={v => { const c = v.replace(/\D/g, ""); set("insuranceRatePct", c === "" ? null : Number(c)); }} />
          </Field>
        </div>
      </Card>

      {anyEv5 && (() => {
        // v1.7.7 Wave 4: replacement timing is now cycle-based (see
        // vehicleCalc in data.jsx) -- pull the already-computed figures for
        // whichever side is EV instead of recomputing here.
        const R5 = window.computeTCO(s);
        const evSide = vA5?.powertrain === "EV" ? R5?.A : R5?.B;
        const evVeh = vA5?.powertrain === "EV" ? vA5 : vB5;
        const cycleLifeStandard = evVeh?.batteryCycleLife ?? 4000;
        return (
          <Card title="EV Battery Replacement" idSub="Penggantian Baterai EV"
            head={<span className="assumption-tag">
              {tr(lang, "Cycle-based — replacement cost off by default", "Berbasis siklus — biaya penggantian nonaktif secara default")}
              <InfoHint note={tr(lang,
                "Replacement timing is derived from this scenario's actual annual km divided by the vehicle's usable range per charge cycle, against its cycle-life standard (Screen 2 → Vehicle Specifications, default 4000 cycles if not overridden) — not a calendar assumption. Doesn't change any cost unless you turn on \"Model battery replacement cost\" below.",
                "Waktu penggantian diturunkan dari jarak tempuh tahunan skenario ini dibagi jangkauan terpakai per siklus pengisian kendaraan, terhadap standar siklus hidupnya (Layar 2 → Spesifikasi Kendaraan, default 4000 siklus jika tidak diubah) — bukan asumsi kalender. Tidak mengubah biaya apa pun kecuali Anda mengaktifkan \"Modelkan biaya penggantian baterai\" di bawah.")} />
            </span>}>
            <div className="grid-3">
              <Field en="Cycle Life Standard" id="Standar Siklus Hidup"
                helpEn="This EV's rated full-charge-cycle life — edit per vehicle in Screen 2 → Vehicle Specifications."
                help="Siklus hidup pengisian penuh terdaftar kendaraan EV ini — edit per kendaraan di Layar 2 → Spesifikasi Kendaraan.">
                <div className="affix readonly">
                  <input value={`${fmt.num(cycleLifeStandard)} ${tr(lang, "cycles", "siklus")}`} readOnly />
                </div>
              </Field>
              <Field en="Cycles / Year" id="Siklus / Tahun"
                helpEn="Derived from this scenario's annual km ÷ usable range per charge cycle (20-80% SOC)."
                help="Diturunkan dari jarak tempuh tahunan skenario ini ÷ jangkauan terpakai per siklus pengisian (SOC 20-80%).">
                <div className="affix readonly">
                  <input value={evSide ? evSide.batteryCyclesPerYear.toFixed(1) : "—"} readOnly />
                </div>
              </Field>
              <Field en="Est. Replacement Year" id="Estimasi Tahun Penggantian"
                helpEn="First year accumulated cycles reach the cycle-life standard."
                help="Tahun pertama akumulasi siklus mencapai standar siklus hidup.">
                <div className="affix readonly">
                  <input value={evSide?.batteryReplacementYear != null ? tr(lang, `Year ${evSide.batteryReplacementYear}`, `Tahun ${evSide.batteryReplacementYear}`) : tr(lang, "Not within horizon", "Tidak dalam horizon")} readOnly />
                </div>
              </Field>
            </div>
            <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", marginTop: 14 }}>
              <input type="checkbox" checked={!!s.modelBatteryReplacement}
                onChange={e => set("modelBatteryReplacement", e.target.checked)} />
              {tr(lang,
                "Model battery replacement cost in TCO (adds a one-time cost in the estimated replacement year above, if within the horizon)",
                "Modelkan biaya penggantian baterai dalam TCO (menambahkan biaya satu kali pada tahun penggantian estimasi di atas, jika dalam horizon)")}
            </label>
            {s.modelBatteryReplacement && (
              <div className="grid-2" style={{ marginTop: 12 }}>
                <Field en="Battery Pack Cost" id="Biaya Paket Baterai"
                  helpEn="Assumption: battery pack cost as a % of this EV's purchase price."
                  help="Asumsi: biaya paket baterai sebagai % dari harga beli EV ini.">
                  <AffixInput value={fmt.num(s.batteryPackCostPct ?? 35)} suffix="% of price"
                    onChange={v => set("batteryPackCostPct", Number(v.replace(/[^\d.]/g, "")) || 0)} />
                </Field>
              </div>
            )}
            {/* Unrelated to replacement timing above (that's cycle-based
                now) -- this still feeds the SOH-linked Residual Value curve
                (window.residualFractionSOH, Financials Audit tab), a
                separate, still-live use of this same input. Kept editable
                here since this card is the only UI for it. */}
            <div style={{ marginTop: 14, paddingTop: 12, borderTop: "1px solid var(--border)" }}>
              <Field en="Degradation Rate (for Residual Value)" id="Tingkat Degradasi (untuk Nilai Sisa)"
                helpEn="Assumed annual battery capacity fade — feeds the SOH-linked EV Residual Value curve only (Financials Audit tab), not the replacement timing above."
                help="Asumsi penurunan kapasitas baterai tahunan — hanya memengaruhi kurva Nilai Sisa EV berbasis SOH (tab Audit Keuangan), bukan waktu penggantian di atas.">
                <AffixInput value={fmt.num(s.batteryDegradationPctPerYear ?? 2.5)} suffix="%/thn"
                  onChange={v => set("batteryDegradationPctPerYear", Number(v.replace(/[^\d.]/g, "")) || 0)} />
              </Field>
            </div>
          </Card>
        );
      })()}

      {anyEv5 && (
        <Card title="Life-Cycle CO2 (Embodied Manufacturing)" idSub="CO2 Siklus Hidup (Manufaktur Tertanam)"
          head={<span className="assumption-tag">
            {tr(lang, "Off by default — operational-only CO2 unaffected", "Nonaktif secara default — CO2 operasional saja tidak terpengaruh")}
            <InfoHint note={tr(lang,
              "The headline CO2 Reduction figure on Results is operational (well-to-wheel) only. Turning this on adds a one-time battery-manufacturing footprint for the EV side and a carbon payback distance — shown separately in Results → Details, never blended into the headline number. ICE vehicle manufacturing CO2 is not modeled (no reliable per-vehicle default exists for this catalog) and is shown as “not modeled” rather than assumed zero.",
              "Angka Reduksi CO2 utama di Hasil hanya operasional (well-to-wheel). Mengaktifkan ini menambahkan jejak manufaktur baterai satu kali untuk sisi EV dan jarak balik modal karbon — ditampilkan terpisah di Hasil → Detail, tidak pernah dicampur ke angka utama. CO2 manufaktur kendaraan ICE tidak dimodelkan (tidak ada default per-kendaraan yang andal untuk katalog ini) dan ditampilkan sebagai “tidak dimodelkan” bukan diasumsikan nol.")} />
          </span>}>
          <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
            <input type="checkbox" checked={!!s.includeEmbodiedCo2}
              onChange={e => set("includeEmbodiedCo2", e.target.checked)} />
            {tr(lang,
              "Include embodied battery-manufacturing CO2 for the EV side (see Results → Details → CO2 / Emissions Breakdown)",
              "Sertakan CO2 manufaktur baterai tertanam untuk sisi EV (lihat Hasil → Detail → Rincian CO2 / Emisi)")}
          </label>
          {s.includeEmbodiedCo2 && (
            <div className="grid-3" style={{ marginTop: 12 }}>
              <Field en="Battery Mfg. CO2 Factor" id="Faktor CO2 Manufaktur Baterai"
                helpEn="kgCO2 per kWh of battery capacity, cradle-to-gate. Default 74 = NMC811 cradle-to-gate median (peer-reviewed battery LCA studies) — pending Indonesia-specific battery supply chain data."
                help="kgCO2 per kWh kapasitas baterai, cradle-to-gate. Default 74 = median cradle-to-gate NMC811 (studi LCA baterai peer-review) — menunggu data rantai pasok baterai spesifik Indonesia.">
                <AffixInput value={fmt.num(s.evBatteryMfgCo2PerKwh ?? 74)} suffix="kgCO2/kWh"
                  onChange={v => set("evBatteryMfgCo2PerKwh", Number(v.replace(/[^\d.]/g, "")) || 0)} />
              </Field>
            </div>
          )}
        </Card>
      )}

      <Card>
        <div className={"collapse-head" + (advOpen ? " open" : "")} onClick={() => setAdvOpen(o => !o)}>
          <span className="chev">
            <svg width="11" height="11" viewBox="0 0 12 12" fill="none">
              <path d="M4 2l4 4-4 4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </span>
          <span style={{ fontWeight: 700, fontSize: 15, color: "var(--c-primary)" }}>
            ⚙ {tr(lang, "Advanced Assumptions", "Asumsi Lanjutan")}
          </span>
          <span style={{ fontSize: 12, color: "var(--text-muted)" }}>({tr(lang, "optional", "opsional")})</span>
        </div>
        <div className="collapse-body" style={{ maxHeight: advOpen ? 520 : 0, transition: "max-height .3s ease", marginTop: advOpen ? 18 : 0 }}>
          <div className="grid-2">
            <Field en="Discount Rate / WACC" id="Tingkat Diskonto">
              <AffixInput value={s.wacc} suffix="% / thn" onChange={v => set("wacc", Number(v.replace(/[^\d.]/g, "")) || 0)} />
            </Field>
            <Field en="Carbon Credit" id="Kredit Karbon">
              <AffixInput value={fmt.num(s.carbon)} prefix="Rp" suffix="/ton CO₂"
                onChange={v => set("carbon", Number(v.replace(/\D/g, "")) || 0)} />
            </Field>
          </div>

          <div className="adblue-block">
            <div className="adblue-head">
              <span className="ab-title">{tr(lang, "AdBlue (DEF) — ICE only", "AdBlue (DEF) — hanya ICE")}</span>
              <span className={"ab-mode " + (s.adblue > 0 ? "on" : "off")}>
                {s.adblue > 0
                  ? tr(lang, `diesel + AdBlue · ${s.adblueDose}% dose`, `solar + AdBlue · dosis ${s.adblueDose}%`)
                  : tr(lang, "pure diesel", "solar murni")}
              </span>
            </div>
            <div className="grid-2">
              <Field en="AdBlue Price" id="Harga AdBlue" opt
                help="Set 0 → ICE pakai solar murni (tanpa AdBlue). Isi > 0 → skenario diesel + AdBlue."
                helpEn="Set 0 → all ICE run pure diesel (no AdBlue). Enter > 0 → diesel + AdBlue scenario.">
                <AffixInput value={fmt.num(s.adblue)} prefix="Rp" suffix="/liter"
                  onChange={v => set("adblue", Number(v.replace(/\D/g, "")) || 0)} />
              </Field>
              <Field en="AdBlue Dose" id="Dosis AdBlue"
                help={"% dari volume solar (umumnya 3–5% untuk mesin SCR)." + (s.adblue > 0 ? "" : " Tidak berlaku saat harga 0.")}
                helpEn={"% of diesel volume (typically 3–5% for SCR engines)." + (s.adblue > 0 ? "" : " Inactive while price is 0.")}>
                <div className="slider-wrap" style={{ marginTop: 0 }}>
                  <input type="range" className="range" min={0} max={10} step={0.5} value={s.adblueDose}
                    disabled={s.adblue <= 0}
                    style={{
                      background: `linear-gradient(to right, var(--c-accent) 0%, var(--c-accent) ${(s.adblueDose / 10) * 100}%, var(--border) ${(s.adblueDose / 10) * 100}%, var(--border) 100%)`,
                      opacity: s.adblue <= 0 ? .45 : 1
                    }}
                    onChange={e => set("adblueDose", Number(e.target.value))} />
                  <div className="affix" style={{ width: 92, flex: "none" }}>
                    <input value={s.adblueDose} inputMode="decimal" disabled={s.adblue <= 0}
                      style={{ textAlign: "center" }}
                      onChange={e => { let n = parseFloat(e.target.value); if (isNaN(n)) n = 0; set("adblueDose", Math.max(0, Math.min(10, n))); }} />
                    <span className="fix suf">%</span>
                  </div>
                </div>
              </Field>
            </div>
          </div>
        </div>
      </Card>

      <Card title="Expert Financial Parameters" idSub="Parameter Keuangan Ahli">
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 12 }}>
          <ExpertToggle isOn={!!s.screen5_expertMode} onToggle={v => set("screen5_expertMode", v)} />
        </div>

        {s.screen5_expertMode && (
          <>
            <CollapsibleSection title={tr(lang, "Financing Structure", "Struktur Pembiayaan")} defaultOpen>
              <div className="grid-3">
                {finPctField("cashRatio", "Cash Ratio", "Rasio Tunai")}
                {finPctField("loanRatio", "Loan Ratio", "Rasio Kredit")}
                {finPctField("leaseRatio", "Lease Ratio", "Rasio Sewa")}
              </div>
              {ratioSum !== 100 && (
                <WarnHint label={tr(lang, "Ratios ≠ 100%", "Rasio ≠ 100%")}
                  note={tr(lang, `Ratios must sum to 100% (currently ${ratioSum}%).`, `Rasio harus berjumlah 100% (saat ini ${ratioSum}%).`)} />
              )}
            </CollapsibleSection>

            <CollapsibleSection title={tr(lang, "Depreciation", "Depresiasi")}>
              <div className="grid-3">
                <Field en="Method" id="Metode">
                  <Select value="straight-line" onChange={() => {}}
                    options={[{ value: "straight-line", label: tr(lang, "Straight-Line", "Garis Lurus") }]} />
                  <span className="assumption-tag">{tr(lang, "assumption", "asumsi")}</span>
                </Field>
                {finYearsField("vehicleAssetLifeYears", "Vehicle Asset Life", "Usia Aset Kendaraan")}
                {finYearsField("chargerAssetLifeYears", "Charger Asset Life", "Usia Aset Charger")}
                {finPctField("residualValuePct", "Residual Value", "Nilai Sisa")}
              </div>
              <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer", marginTop: 14 }}>
                <input type="checkbox" checked={!!s.includeResidualInTco}
                  onChange={e => set("includeResidualInTco", e.target.checked)} />
                {tr(lang,
                  "Net residual value into Total TCO and Total Savings",
                  "Kurangkan nilai sisa dari Total TCO dan Total Penghematan")}
              </label>
              <p className="field-help" style={{ marginTop: 6, display: "flex", alignItems: "center" }}>
                {tr(lang, "Off by default", "Nonaktif secara default")}
                <InfoHint note={tr(lang,
                  "Matches how VKTR's own TCOO analyses report savings — Total TCO and Total Savings reflect cash cost only (CAPEX, financing, energy, maintenance, infra, insurance, battery), with no credit for the vehicle's end-of-horizon resale value. The \"Residual Value\" row below still shows the estimate for reference, and the Monthly Cost / Unit KPI still nets it (true depreciation), but it is not subtracted from Total TCO / Total Savings unless this is turned on.",
                  "Sesuai cara VKTR melaporkan penghematan pada analisis TCOO sendiri — Total TCO dan Total Penghematan hanya mencerminkan biaya kas (CAPEX, pembiayaan, energi, perawatan, infrastruktur, asuransi, baterai), tanpa kredit nilai jual kembali kendaraan di akhir horizon. Baris \"Nilai Sisa\" di bawah tetap menampilkan estimasinya sebagai referensi, dan KPI Biaya Bulanan/Unit tetap memperhitungkannya (depresiasi sebenarnya), tapi tidak dikurangkan dari Total TCO / Total Penghematan kecuali diaktifkan.")} />
              </p>
            </CollapsibleSection>

            <CollapsibleSection title={tr(lang, "Escalation Rates (%/year)", "Tingkat Eskalasi (%/tahun)")}>
              <div className="grid-3">
                {finPctField("electricityEscalation", "Electricity", "Listrik")}
                {finPctField("dieselEscalation", "Diesel", "Solar")}
                {finPctField("umrEscalation", "UMR", "UMR")}
              </div>
            </CollapsibleSection>

            <CollapsibleSection title={tr(lang, "Carbon Credits", "Kredit Karbon")}>
              <div className="grid-2">
                <Field en="Pricing Model" id="Model Harga">
                  <Select value="flat" onChange={() => {}}
                    options={[{ value: "flat", label: tr(lang, "Flat", "Tetap") }]} />
                  <span className="assumption-tag">{tr(lang, "assumption", "asumsi")}</span>
                </Field>
                <Field en="Price (IDR/ton CO₂)" id="Harga (IDR/ton CO₂)">
                  <AffixInput value={fmt.num(s.carbon)} prefix="Rp" suffix="/ton CO₂"
                    onChange={v => set("carbon", Number(v.replace(/\D/g, "")) || 0)} />
                  <ValueFlag defaultValue={fdef("carbonCreditPricePerTon")} currentValue={s.carbon}
                    onReset={() => set("carbon", fdef("carbonCreditPricePerTon"))} />
                </Field>
              </div>
            </CollapsibleSection>


            <div style={{ marginTop: 14 }}>
              <label className="future-tag" style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 12px" }}>
                🔒 {tr(lang, "Time-of-Use Tariff Modeling — Coming in future version", "Pemodelan Tarif Time-of-Use — Akan hadir di versi mendatang")}
              </label>
            </div>
          </>
        )}
      </Card>
      </>}
    </>
  );
}

Object.assign(window, { Screen1, Screen2, Screen3, Screen4, Screen5, PRESET_SAVE_EXCLUDE_KEYS });
