/* global React, Icon, Modal */
const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React;

// NOTE: no class-based ErrorBoundary here. An earlier attempt to add one
// (with `class extends React.Component`) appeared to break Babel standalone
// parsing of this whole file in some environments, leaving window.Activities
// undefined. We rely on defensive coding + console logging instead.

// ============================================================================
// ACTIVITIES SCREEN (E3) · gestion des activités projet
// ----------------------------------------------------------------------------
// 1 activité = 1 projet (FK obligatoire — donc le champ « PROJET » est toujours
// connu et apparaît systématiquement en premier dans le modal de saisie).
//
// Cette page permet de :
//   • lister les activités (filtrables par projet, statut, type, dates)
//   • créer / éditer / supprimer une activité avec tous les champs métier
//   • saisir GPS (latitude/longitude), partenaires, indicateurs, pièces jointes
//   • cocher PRESSE / TDRs / Rapport
//   • ajouter à la volée un nouveau type d'activité, partenaire ou composante
//     projet (sans quitter le modal)
// ============================================================================

// Status options shared by filter + modal. Order = workflow order.
const ACTIVITY_STATUSES = [
  { v: "not_started", fr: "Non démarrée",   en: "Not started",  color: "oklch(0.78 0.05 250)" },
  { v: "ongoing",     fr: "En cours",       en: "Ongoing",      color: "oklch(0.55 0.16 230)" },
  { v: "completed",   fr: "Terminée",       en: "Completed",    color: "oklch(0.55 0.16 145)" },
  { v: "postponed",   fr: "Reportée",       en: "Postponed",    color: "oklch(0.65 0.16 70)"  },
  { v: "cancelled",   fr: "Annulée",        en: "Cancelled",    color: "oklch(0.55 0.16 12)"  },
];
function statusLabel(slug, lang) {
  const s = ACTIVITY_STATUSES.find((x) => x.v === slug);
  if (!s) return slug || "";
  return lang === "fr" ? s.fr : s.en;
}
function statusColor(slug) {
  const s = ACTIVITY_STATUSES.find((x) => x.v === slug);
  return s ? s.color : "var(--text-faint)";
}

function Activities({ t, lang, isSuperAdmin, effectiveOrgId, isAdmin, hasPerm }) {
  // ── Data ────────────────────────────────────────────────────────────────
  const { projects, loading: projectsLoading } = window.melr.useProjects();
  const { data: activities, loading } = window.melr.useActivities(null); // all projects
  const { data: types }      = window.melr.useActivityTypes();
  const { data: partners }   = window.melr.usePartners();

  // Visible projects in the org context. mapProjectRow exposes the row as
  // { uuid, id (= legacy code), nameFr, nameEn, organizationId, ... } — NOT
  // the raw snake_case from Supabase. We key everything off `uuid` since
  // activities.project_id holds the UUID FK.
  // For super-admin acting-as-org we additionally narrow client-side.
  const visibleProjects = useMemoA(() => {
    const list = projects || [];
    if (!effectiveOrgId || isSuperAdmin) return list;
    return list.filter((p) => !p.organizationId || p.organizationId === effectiveOrgId);
  }, [projects, effectiveOrgId, isSuperAdmin]);

  // ── Filters ─────────────────────────────────────────────────────────────
  const [fProject,  setFProject]  = useStateA("");
  const [fStatus,   setFStatus]   = useStateA("");
  const [fType,     setFType]     = useStateA("");
  const [fSearch,   setFSearch]   = useStateA("");
  const [editing,   setEditing]   = useStateA(null);   // null | "new" | row.id
  const [busy,      setBusy]      = useStateA(null);
  const [toast,     setToast]     = useStateA(null);

  const visibleActivities = useMemoA(() => {
    // a.project_id is a UUID — match against p.uuid (the actual Supabase id),
    // NOT p.id (which is the legacy code like "P-241").
    const orgProjectUuids = new Set(visibleProjects.map((p) => p.uuid));
    return (activities || []).filter((a) => {
      if (orgProjectUuids.size && !orgProjectUuids.has(a.project_id) && !isSuperAdmin) return false;
      if (fProject && a.project_id !== fProject) return false;
      if (fStatus  && a.status     !== fStatus)  return false;
      if (fType    && a.activity_type_id !== fType) return false;
      if (fSearch) {
        const q = fSearch.toLowerCase();
        const hay = `${a.title || ""} ${a.description || ""} ${a.location_name || ""} ${a.code || ""}`.toLowerCase();
        if (!hay.includes(q)) return false;
      }
      return true;
    });
  }, [activities, visibleProjects, fProject, fStatus, fType, fSearch, isSuperAdmin]);

  // Keyed by p.uuid so we can look up project metadata from activity.project_id
  const projectsByUuid = useMemoA(() => Object.fromEntries(visibleProjects.map((p) => [p.uuid, p])), [visibleProjects]);
  const typesById      = useMemoA(() => Object.fromEntries((types || []).map((t) => [t.id, t])),     [types]);

  const onDelete = async (a) => {
    if (!window.confirm(lang === "fr"
      ? `Supprimer l'activité « ${a.title} » ? Cette action est irréversible.`
      : `Delete activity "${a.title}"? This action is irreversible.`)) return;
    setBusy(a.id);
    try {
      await window.melr.activitiesCrud.remove(a.id);
      setToast({ ok: true, msg: lang === "fr" ? "Activité supprimée." : "Activity deleted." });
    } catch (e) {
      setToast({ ok: false, msg: e.message });
    } finally {
      setBusy(null);
      setTimeout(() => setToast(null), 3500);
    }
  };

  const editingRow = editing && editing !== "new"
    ? (activities || []).find((a) => a.id === editing)
    : null;

  // ── Render ─────────────────────────────────────────────────────────────
  return (
    <div className="screen" data-screen-label="Activities">
      <div className="row" style={{ alignItems: "center", marginBottom: 12 }}>
        <div>
          <div className="screen-title">{lang === "fr" ? "Activités" : "Activities"}</div>
          <div className="text-faint" style={{ fontSize: 12, marginTop: 2 }}>
            {lang === "fr"
              ? "Suivi des activités de mise en œuvre par projet (formations, visites, ateliers, etc.)"
              : "Tracking implementation activities by project (training, visits, workshops, etc.)"}
          </div>
        </div>
        <div style={{ flex: 1 }} />
        <button className="btn sm primary" onClick={() => setEditing("new")}>
          <Icon.plus /> {lang === "fr" ? "Nouvelle activité" : "New activity"}
        </button>
      </div>

      {/* Filter bar */}
      <div className="card" style={{ marginBottom: 12 }}>
        <div className="card-body" style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr 1fr 2fr", gap: 8 }}>
          <select className="input" value={fProject} onChange={(e) => setFProject(e.target.value)}>
            <option value="">{lang === "fr" ? "— Tous les projets —" : "— All projects —"}</option>
            {visibleProjects.map((p) => (
              <option key={p.uuid} value={p.uuid}>{p.id} · {lang === "fr" ? (p.nameFr || p.nameEn) : (p.nameEn || p.nameFr)}</option>
            ))}
          </select>
          <select className="input" value={fStatus} onChange={(e) => setFStatus(e.target.value)}>
            <option value="">{lang === "fr" ? "— Tous statuts —" : "— All statuses —"}</option>
            {ACTIVITY_STATUSES.map((s) => (
              <option key={s.v} value={s.v}>{lang === "fr" ? s.fr : s.en}</option>
            ))}
          </select>
          <select className="input" value={fType} onChange={(e) => setFType(e.target.value)}>
            <option value="">{lang === "fr" ? "— Tous types —" : "— All types —"}</option>
            {(types || []).map((tt) => (
              <option key={tt.id} value={tt.id}>{lang === "fr" ? tt.name_fr : (tt.name_en || tt.name_fr)}</option>
            ))}
          </select>
          <input className="input" type="search" placeholder={lang === "fr" ? "Recherche (titre, description, lieu…)" : "Search (title, description, location…)"}
            value={fSearch} onChange={(e) => setFSearch(e.target.value)} />
        </div>
      </div>

      {toast && (
        <div style={{
          marginBottom: 8, padding: "8px 12px", borderRadius: 6, fontSize: 12.5,
          background: toast.ok ? "#dcfce7" : "#fee2e2",
          color:      toast.ok ? "#166534" : "#991b1b",
        }}>{toast.msg}</div>
      )}

      <div className="card">
        <div className="card-body flush" style={{
          overflowX: "auto", overflowY: "auto",
          maxHeight: "calc(100vh - 280px)",
        }}>
          {loading || projectsLoading ? (
            <div className="text-faint" style={{ padding: 24, textAlign: "center" }}>{lang === "fr" ? "Chargement…" : "Loading…"}</div>
          ) : visibleActivities.length === 0 ? (
            <div className="text-faint" style={{ padding: 24, textAlign: "center" }}>
              {lang === "fr"
                ? "Aucune activité. Cliquez sur « Nouvelle activité » pour en créer une."
                : "No activities yet. Click 'New activity' to create one."}
            </div>
          ) : (
            <table className="tbl" style={{ minWidth: "100%" }}>
              <thead>
                <tr>
                  <th>{lang === "fr" ? "Projet" : "Project"}</th>
                  <th>{lang === "fr" ? "Titre" : "Title"}</th>
                  <th>{lang === "fr" ? "Type" : "Type"}</th>
                  <th>{lang === "fr" ? "Lieu" : "Location"}</th>
                  <th>{lang === "fr" ? "Début" : "Start"}</th>
                  <th>{lang === "fr" ? "Fin" : "End"}</th>
                  <th>{lang === "fr" ? "Statut" : "Status"}</th>
                  <th style={{
                    position: "sticky", right: 0, zIndex: 2,
                    minWidth: 140, background: "var(--bg-sunken)",
                    boxShadow: "-4px 0 6px -4px rgba(0,0,0,0.08)",
                  }}>{lang === "fr" ? "Actions" : "Actions"}</th>
                </tr>
              </thead>
              <tbody>
                {visibleActivities.map((a) => {
                  const proj = projectsByUuid[a.project_id];
                  const type = typesById[a.activity_type_id];
                  return (
                    <tr key={a.id}>
                      <td className="mono" style={{ fontSize: 11.5 }}>
                        {/* p.id IS the legacy code (project code like "P-241"). */}
                        {proj ? proj.id : <span className="text-faint">—</span>}
                      </td>
                      <td>
                        <div style={{ fontWeight: 500 }}>{a.title}</div>
                        {a.code && <div className="text-faint mono" style={{ fontSize: 10.5 }}>{a.code}</div>}
                      </td>
                      <td>{type
                        ? <span className="pill" style={{ fontSize: 10.5 }}>{lang === "fr" ? type.name_fr : (type.name_en || type.name_fr)}</span>
                        : <span className="text-faint">—</span>}</td>
                      <td className="text-faint" style={{ fontSize: 11 }}>
                        {(() => {
                          if (a.location_name) return a.location_name;
                          // Coerce defensively in case PostgREST returns numeric as string.
                          const la = Number(a.latitude), lo = Number(a.longitude);
                          if (Number.isFinite(la) && Number.isFinite(lo)) {
                            return `${la.toFixed(3)}, ${lo.toFixed(3)}`;
                          }
                          return "—";
                        })()}
                      </td>
                      <td className="text-faint" style={{ fontSize: 11 }}>{a.start_date || "—"}</td>
                      <td className="text-faint" style={{ fontSize: 11 }}>{a.end_date   || "—"}</td>
                      <td>
                        <span className="pill" style={{ fontSize: 10.5, background: statusColor(a.status), color: "white" }}>
                          {statusLabel(a.status, lang)}
                        </span>
                      </td>
                      <td style={{
                        position: "sticky", right: 0,
                        background: "var(--bg-elev, white)",
                        boxShadow: "-4px 0 6px -4px rgba(0,0,0,0.08)",
                        minWidth: 140,
                      }}>
                        <div className="row gap-xs" style={{ justifyContent: "flex-end" }}>
                          <button className="btn sm primary" onClick={() => setEditing(a.id)}
                            style={{ fontSize: 11.5, padding: "3px 10px" }}>
                            <Icon.edit /> {lang === "fr" ? "Modifier" : "Edit"}
                          </button>
                          <button className="btn xs ghost" onClick={() => onDelete(a)} disabled={busy === a.id}
                            title={lang === "fr" ? "Supprimer" : "Delete"}>
                            <Icon.x />
                          </button>
                        </div>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>

      {editing && (
        <ActivityEditorModal
          lang={lang}
          existing={editingRow}
          visibleProjects={visibleProjects}
          isAdmin={isAdmin || isSuperAdmin}
          onClose={() => setEditing(null)}
          onSaved={() => setEditing(null)}
        />
      )}
    </div>
  );
}

// ============================================================================
// ACTIVITY EDITOR MODAL · all the metadata + multi-pickers + attachments
// ============================================================================
function ActivityEditorModal({ lang, existing, visibleProjects, isAdmin, onClose, onSaved }) {
  const isNew = !existing;
  // Stateful current id so the attachments section becomes available
  // immediately after first save without closing/reopening the modal.
  const [currentId, setCurrentId] = useStateA(existing ? existing.id : null);

  // ── Core fields ────────────────────────────────────────────────────────
  // PROJET is always the first field — required by spec ("le champ PROJET en
  // première ligne du formulaire toujours"). Empty by default for new
  // activities; pre-filled when editing.
  const [projectId,  setProjectId]  = useStateA(existing ? existing.project_id : "");
  const [code,       setCode]       = useStateA(existing ? (existing.code || "")        : "");
  const [title,      setTitle]      = useStateA(existing ? (existing.title || "")       : "");
  const [description,setDescription]= useStateA(existing ? (existing.description || "") : "");
  const [typeId,     setTypeId]     = useStateA(existing ? (existing.activity_type_id || "") : "");
  const [componentId,setComponentId]= useStateA(existing ? (existing.project_component_id || "") : "");
  const [locationName,setLocationName] = useStateA(existing ? (existing.location_name || "") : "");
  const [lat,        setLat]        = useStateA(existing && existing.latitude  != null ? String(existing.latitude)  : "");
  const [lng,        setLng]        = useStateA(existing && existing.longitude != null ? String(existing.longitude) : "");
  const [startDate,  setStartDate]  = useStateA(existing ? (existing.start_date || "") : "");
  const [endDate,    setEndDate]    = useStateA(existing ? (existing.end_date   || "") : "");
  const [status,     setStatus]     = useStateA(existing ? (existing.status || "not_started") : "not_started");
  const [genderSensitive, setGenderSensitive] = useStateA(existing ? !!existing.gender_sensitive : false);
  const [budgetPlanned, setBudgetPlanned] = useStateA(existing && existing.budget_planned != null ? String(existing.budget_planned) : "");
  const [costActual,    setCostActual]    = useStateA(existing && existing.cost_actual    != null ? String(existing.cost_actual)    : "");
  const [currency,   setCurrency]   = useStateA(existing ? (existing.currency || "XOF") : "XOF");
  const [hasPress,   setHasPress]   = useStateA(existing ? !!existing.has_press  : false);
  const [hasTdrs,    setHasTdrs]    = useStateA(existing ? !!existing.has_tdrs   : false);
  const [hasReport,  setHasReport]  = useStateA(existing ? !!existing.has_report : false);
  const [notes,      setNotes]      = useStateA(existing ? (existing.notes || "") : "");

  // ── Linked entities (loaded on edit, empty on create) ──────────────────
  // hydrated tracks "did we already sync once from the server for THIS
  // currentId?" — without this guard the effect re-fires whenever links
  // arrive late and overwrites the user's mid-edit selections with the
  // stale empty array from the previous activityId. The Map remembers,
  // per currentId, whether we've already done the initial fetch+sync.
  const { partners: linkedPartners, indicatorIds: linkedIndicators, loading: linksLoading } =
    window.melr.useActivityLinks(currentId);
  const [partnerIds, setPartnerIds] = useStateA([]);
  const [indicatorIds, setIndicatorIds] = useStateA([]);
  const [hydratedFor, setHydratedFor] = useStateA(null);
  useEffectA(() => {
    if (!currentId) { setHydratedFor(null); return; }
    if (linksLoading) return;
    if (hydratedFor === currentId) return;  // already synced for this row
    setPartnerIds((linkedPartners || []).map((p) => p.partner_id));
    setIndicatorIds(linkedIndicators || []);
    setHydratedFor(currentId);
  }, [linksLoading, currentId, linkedPartners, linkedIndicators, hydratedFor]);

  const [busy, setBusy] = useStateA(false);
  const [err, setErr]   = useStateA(null);

  const onSubmit = async (e) => {
    e.preventDefault();
    e.stopPropagation && e.stopPropagation();
    if (!projectId)     { setErr(lang === "fr" ? "Veuillez choisir un projet." : "Please pick a project."); return; }
    if (!title.trim())  { setErr(lang === "fr" ? "Le titre est requis." : "Title is required."); return; }
    setBusy(true); setErr(null);
    const payload = {
      project_id:            projectId,
      code:                  code.trim() || null,
      title:                 title.trim(),
      description:           description.trim() || null,
      activity_type_id:      typeId || null,
      project_component_id:  componentId || null,
      location_name:         locationName.trim() || null,
      latitude:              lat,
      longitude:             lng,
      start_date:            startDate || null,
      end_date:              endDate || null,
      status,
      gender_sensitive:      genderSensitive,
      budget_planned:        budgetPlanned,
      cost_actual:           costActual,
      currency,
      has_press:             hasPress,
      has_tdrs:              hasTdrs,
      has_report:            hasReport,
      notes:                 notes.trim() || null,
      partner_ids:           partnerIds,
      indicator_ids:         indicatorIds,
    };
    // eslint-disable-next-line no-console
    console.log("[Activities] submit payload:", payload);
    try {
      let saved;
      if (!currentId) {
        saved = await window.melr.activitiesCrud.create(payload);
        // eslint-disable-next-line no-console
        console.log("[Activities] created:", saved);
        setCurrentId(saved.id);
      } else {
        saved = await window.melr.activitiesCrud.update(currentId, payload);
        // eslint-disable-next-line no-console
        console.log("[Activities] updated:", saved);
      }
      // Keep modal open after first create so user can immediately attach
      // files (TDRs, photos, etc.) without re-navigating.
      if (currentId && onSaved) onSaved();
    } catch (e2) {
      // eslint-disable-next-line no-console
      console.error("[Activities] submit failed:", e2);
      setErr(e2.message || String(e2));
    } finally { setBusy(false); }
  };

  return (
    <Modal
      size="lg"
      title={isNew
        ? (lang === "fr" ? "Nouvelle activité" : "New activity")
        : (lang === "fr" ? "Modifier l'activité" : "Edit activity")}
      onClose={onClose}
      onSubmit={onSubmit}
      footer={<>
        <button type="button" className="btn sm ghost" onClick={onClose} disabled={busy}>
          {lang === "fr" ? "Fermer" : "Close"}
        </button>
        <button type="submit" className="btn sm primary" disabled={busy}>
          {busy ? "…" : (currentId ? (lang === "fr" ? "Enregistrer" : "Save") : (lang === "fr" ? "Créer" : "Create"))}
        </button>
      </>}>
      <ActivityFormBody
        lang={lang}
        currentId={currentId}
        projectId={projectId}  setProjectId={setProjectId}
        visibleProjects={visibleProjects}
        code={code} setCode={setCode}
        title={title} setTitle={setTitle}
        description={description} setDescription={setDescription}
        typeId={typeId} setTypeId={setTypeId}
        componentId={componentId} setComponentId={setComponentId}
        locationName={locationName} setLocationName={setLocationName}
        lat={lat} setLat={setLat} lng={lng} setLng={setLng}
        startDate={startDate} setStartDate={setStartDate}
        endDate={endDate} setEndDate={setEndDate}
        status={status} setStatus={setStatus}
        genderSensitive={genderSensitive} setGenderSensitive={setGenderSensitive}
        budgetPlanned={budgetPlanned} setBudgetPlanned={setBudgetPlanned}
        costActual={costActual} setCostActual={setCostActual}
        currency={currency} setCurrency={setCurrency}
        hasPress={hasPress} setHasPress={setHasPress}
        hasTdrs={hasTdrs}   setHasTdrs={setHasTdrs}
        hasReport={hasReport} setHasReport={setHasReport}
        notes={notes} setNotes={setNotes}
        partnerIds={partnerIds} setPartnerIds={setPartnerIds}
        indicatorIds={indicatorIds} setIndicatorIds={setIndicatorIds}
        isAdmin={isAdmin}
      />
      {err && (
        <div style={{ padding: "8px 10px", background: "#fee2e2", color: "#991b1b", borderRadius: 6, fontSize: 12.5, marginTop: 10 }}>
          {err}
        </div>
      )}
    </Modal>
  );
}

// Extracted body so the modal stays readable. All state lives in the parent
// (ActivityEditorModal); this is a pure render component.
function ActivityFormBody(props) {
  const {
    lang, currentId,
    projectId, setProjectId, visibleProjects,
    code, setCode, title, setTitle, description, setDescription,
    typeId, setTypeId, componentId, setComponentId,
    locationName, setLocationName, lat, setLat, lng, setLng,
    startDate, setStartDate, endDate, setEndDate,
    status, setStatus, genderSensitive, setGenderSensitive,
    budgetPlanned, setBudgetPlanned, costActual, setCostActual, currency, setCurrency,
    hasPress, setHasPress, hasTdrs, setHasTdrs, hasReport, setHasReport,
    notes, setNotes,
    partnerIds, setPartnerIds, indicatorIds, setIndicatorIds,
    isAdmin,
  } = props;

  const inp = { width: "100%", padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--input-bg, var(--bg, white))", color: "var(--text)", boxSizing: "border-box", fontFamily: "inherit" };
  const lbl = { display: "block", fontSize: 11, color: "var(--text-faint)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.04em" };

  // Use device geolocation to fill lat/lng. Optional — many environments
  // block geolocation; show an error pill if it fails.
  const [geoErr, setGeoErr] = useStateA(null);
  const useDeviceLocation = () => {
    if (!navigator.geolocation) { setGeoErr(lang === "fr" ? "Géolocalisation non disponible." : "Geolocation not available."); return; }
    setGeoErr(null);
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setLat(pos.coords.latitude.toFixed(6));
        setLng(pos.coords.longitude.toFixed(6));
      },
      (e) => { setGeoErr(e.message); },
      { enableHighAccuracy: true, timeout: 10000 }
    );
  };

  return (
    <div style={{ display: "grid", gap: 12 }}>
      {/* 1. PROJET (toujours en première ligne) */}
      {/* mapProjectRow shape : p.uuid = real Supabase UUID (FK target),
          p.id = legacy code, p.nameFr/p.nameEn = display name. */}
      <div>
        <label style={lbl}>{lang === "fr" ? "Projet *" : "Project *"}</label>
        <select required style={inp} value={projectId} onChange={(e) => setProjectId(e.target.value)}>
          <option value="">
            {(visibleProjects && visibleProjects.length)
              ? (lang === "fr" ? "— Choisir un projet —" : "— Pick a project —")
              : (lang === "fr" ? "— Aucun projet visible —" : "— No project visible —")}
          </option>
          {visibleProjects.map((p) => (
            <option key={p.uuid} value={p.uuid}>
              {p.id} · {lang === "fr" ? (p.nameFr || p.nameEn) : (p.nameEn || p.nameFr)}
            </option>
          ))}
        </select>
      </div>

      {/* 2. Titre + code */}
      <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 10 }}>
        <div>
          <label style={lbl}>{lang === "fr" ? "Titre *" : "Title *"}</label>
          <input required style={inp} value={title} onChange={(e) => setTitle(e.target.value)}
            placeholder={lang === "fr" ? "Atelier de formation sur…" : "Training workshop on…"} />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Code (optionnel)" : "Code (optional)"}</label>
          <input style={inp} value={code} onChange={(e) => setCode(e.target.value)} placeholder="ACT-001" />
        </div>
      </div>

      {/* 3. Type + composante */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
        <ActivityTypePicker lang={lang} value={typeId} onChange={setTypeId} isAdmin={isAdmin} inp={inp} lbl={lbl} />
        <ProjectComponentPicker lang={lang} projectId={projectId} value={componentId} onChange={setComponentId} isAdmin={isAdmin} inp={inp} lbl={lbl} />
      </div>

      {/* 4. Description */}
      <div>
        <label style={lbl}>{lang === "fr" ? "Description" : "Description"}</label>
        <textarea style={{ ...inp, minHeight: 72, resize: "vertical" }} value={description}
          onChange={(e) => setDescription(e.target.value)}
          placeholder={lang === "fr" ? "Objet, méthodologie, public ciblé…" : "Purpose, methodology, audience…"} />
      </div>

      {/* 5. Lieu + GPS */}
      <div>
        <label style={lbl}>{lang === "fr" ? "Lieu de l'activité" : "Activity location"}</label>
        <input style={inp} value={locationName} onChange={(e) => setLocationName(e.target.value)}
          placeholder={lang === "fr" ? "Dakar, Hôtel Pullman — Salle A" : "Dakar, Pullman Hotel — Room A"} />
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr auto", gap: 10, alignItems: "end" }}>
        <div>
          <label style={lbl}>{lang === "fr" ? "Latitude" : "Latitude"}</label>
          <input style={inp} type="number" step="any" value={lat} onChange={(e) => setLat(e.target.value)} placeholder="14.6928" />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Longitude" : "Longitude"}</label>
          <input style={inp} type="number" step="any" value={lng} onChange={(e) => setLng(e.target.value)} placeholder="-17.4467" />
        </div>
        <button type="button" className="btn sm ghost" onClick={useDeviceLocation}
          title={lang === "fr" ? "Utiliser la position actuelle de l'appareil" : "Use device GPS"}>
          <Icon.pin /> {lang === "fr" ? "GPS" : "GPS"}
        </button>
      </div>
      {geoErr && <div className="text-faint" style={{ fontSize: 11, color: "#b91c1c" }}>⚠ {geoErr}</div>}

      {/* 6. Dates */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
        <div>
          <label style={lbl}>{lang === "fr" ? "Début" : "Start"}</label>
          <input style={inp} type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Fin" : "End"}</label>
          <input style={inp} type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Statut" : "Status"}</label>
          <select style={inp} value={status} onChange={(e) => setStatus(e.target.value)}>
            {ACTIVITY_STATUSES.map((s) => (
              <option key={s.v} value={s.v}>{lang === "fr" ? s.fr : s.en}</option>
            ))}
          </select>
        </div>
      </div>

      {/* 7. Gender sensitive */}
      <label style={{ display: "flex", gap: 8, alignItems: "center", padding: "8px 10px", background: "var(--bg-sunken)", borderRadius: 6, cursor: "pointer" }}>
        <input type="checkbox" checked={genderSensitive} onChange={(e) => setGenderSensitive(e.target.checked)} />
        <span style={{ fontSize: 12.5 }}>
          {lang === "fr" ? "Activité sensible au genre" : "Gender-sensitive activity"}
        </span>
      </label>

      {/* 8. Partenaires (multi) */}
      <PartnersMultiPicker lang={lang} value={partnerIds} onChange={setPartnerIds} isAdmin={isAdmin} inp={inp} lbl={lbl} />

      {/* 9. Indicateurs (multi, scoped to project) */}
      <IndicatorsMultiPicker lang={lang} projectId={projectId} value={indicatorIds} onChange={setIndicatorIds} inp={inp} lbl={lbl} />

      {/* 10. Budget + coût */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 100px", gap: 10 }}>
        <div>
          <label style={lbl}>{lang === "fr" ? "Budget prévu" : "Planned budget"}</label>
          <input style={inp} type="number" step="any" value={budgetPlanned} onChange={(e) => setBudgetPlanned(e.target.value)} placeholder="0" />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Coût réel" : "Actual cost"}</label>
          <input style={inp} type="number" step="any" value={costActual} onChange={(e) => setCostActual(e.target.value)} placeholder="0" />
        </div>
        <div>
          <label style={lbl}>{lang === "fr" ? "Devise" : "Currency"}</label>
          <input style={inp} value={currency} onChange={(e) => setCurrency(e.target.value)} maxLength={6} />
        </div>
      </div>

      {/* 11. Cases PRESSE / TDRs / Rapport */}
      <div>
        <div style={lbl}>{lang === "fr" ? "Livrables confirmés" : "Confirmed deliverables"}</div>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap", padding: "8px 10px", background: "var(--bg-sunken)", borderRadius: 6 }}>
          <label style={{ display: "inline-flex", gap: 6, alignItems: "center", fontSize: 12.5, cursor: "pointer" }}>
            <input type="checkbox" checked={hasPress} onChange={(e) => setHasPress(e.target.checked)} /> PRESSE
          </label>
          <label style={{ display: "inline-flex", gap: 6, alignItems: "center", fontSize: 12.5, cursor: "pointer" }}>
            <input type="checkbox" checked={hasTdrs} onChange={(e) => setHasTdrs(e.target.checked)} /> TDRs
          </label>
          <label style={{ display: "inline-flex", gap: 6, alignItems: "center", fontSize: 12.5, cursor: "pointer" }}>
            <input type="checkbox" checked={hasReport} onChange={(e) => setHasReport(e.target.checked)} /> {lang === "fr" ? "Rapport" : "Report"}
          </label>
        </div>
      </div>

      {/* 12. Notes libres */}
      <div>
        <label style={lbl}>{lang === "fr" ? "Notes" : "Notes"}</label>
        <textarea style={{ ...inp, minHeight: 48, resize: "vertical" }} value={notes}
          onChange={(e) => setNotes(e.target.value)}
          placeholder={lang === "fr" ? "Observations, points d'attention…" : "Observations, points of attention…"} />
      </div>

      {/* 13. Pièces jointes — disponible une fois l'activité créée */}
      <div style={{ borderTop: "1px solid var(--line)", paddingTop: 12, marginTop: 4 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>
          {lang === "fr" ? "Pièces jointes" : "Attachments"}
          <span className="text-faint" style={{ fontSize: 10.5, fontWeight: 400, marginLeft: 8 }}>
            {lang === "fr" ? "TDRs, présences, photos, rapports…" : "TDRs, attendance, photos, reports…"}
          </span>
        </div>
        {currentId ? (
          <ActivityAttachmentsSection activityId={currentId} lang={lang} />
        ) : (
          <div className="text-faint" style={{ fontSize: 11.5, padding: 10, border: "1px dashed var(--line)", borderRadius: 6, textAlign: "center" }}>
            {lang === "fr"
              ? "Enregistrez d'abord l'activité (« Créer »), puis attachez des fichiers ici."
              : "Save the activity first ('Create'), then attach files here."}
          </div>
        )}
      </div>
    </div>
  );
}

// ============================================================================
// LOOKUPS PICKERS · dropdowns with inline "+ Nouveau" sub-modals
// ============================================================================

function ActivityTypePicker({ lang, value, onChange, isAdmin, inp, lbl }) {
  // We grab refresh() so we can force a re-fetch immediately after an
  // inline create — realtime can be unsubscribed (or slow), and we don't
  // want the picker to display the just-created id with no matching option.
  const { data: types, refresh } = window.melr.useActivityTypes();
  const [newOpen, setNewOpen] = useStateA(false);
  return (
    <div>
      <label style={lbl}>{lang === "fr" ? "Type d'activité" : "Activity type"}</label>
      <select style={inp} value={value} onChange={(e) => {
        if (e.target.value === "__NEW__") setNewOpen(true);
        else onChange(e.target.value);
      }}>
        <option value="">{lang === "fr" ? "— Aucun —" : "— None —"}</option>
        {(types || []).map((t) => (
          <option key={t.id} value={t.id}>{lang === "fr" ? t.name_fr : (t.name_en || t.name_fr)}</option>
        ))}
        {isAdmin && <option value="__NEW__">{lang === "fr" ? "+ Nouveau type…" : "+ New type…"}</option>}
      </select>
      {newOpen && (
        <NewActivityTypeModal lang={lang}
          onClose={() => setNewOpen(false)}
          onCreated={async (t) => {
            // Refresh first so the new id is a valid <option>, then select.
            await refresh();
            onChange(t.id);
            setNewOpen(false);
          }} />
      )}
    </div>
  );
}

function ProjectComponentPicker({ lang, projectId, value, onChange, isAdmin, inp, lbl }) {
  const { data: components, refresh } = window.melr.useProjectComponents(projectId);
  const [newOpen, setNewOpen] = useStateA(false);
  return (
    <div>
      <label style={lbl}>{lang === "fr" ? "Composante du projet" : "Project component"}</label>
      <select style={inp} value={value} disabled={!projectId} onChange={(e) => {
        if (e.target.value === "__NEW__") setNewOpen(true);
        else onChange(e.target.value);
      }}>
        <option value="">
          {projectId
            ? (lang === "fr" ? "— Aucune —" : "— None —")
            : (lang === "fr" ? "— Choisir un projet d'abord —" : "— Pick a project first —")}
        </option>
        {(components || []).map((c) => (
          <option key={c.id} value={c.id}>{lang === "fr" ? c.name_fr : (c.name_en || c.name_fr)}</option>
        ))}
        {isAdmin && projectId && <option value="__NEW__">{lang === "fr" ? "+ Nouvelle composante…" : "+ New component…"}</option>}
      </select>
      {newOpen && (
        <NewProjectComponentModal lang={lang} projectId={projectId}
          onClose={() => setNewOpen(false)}
          onCreated={async (c) => {
            // Refresh so the new id matches a <option> BEFORE we select it
            // (without this, the select value points to nothing and the
            // dropdown visually resets to "— Aucune —").
            await refresh();
            onChange(c.id);
            setNewOpen(false);
          }} />
      )}
    </div>
  );
}

function PartnersMultiPicker({ lang, value, onChange, isAdmin, inp, lbl }) {
  const { data: partners, refresh } = window.melr.usePartners();
  const [newOpen, setNewOpen] = useStateA(false);
  const selectedSet = new Set(value || []);
  const toggle = (id) => {
    if (selectedSet.has(id)) onChange(value.filter((x) => x !== id));
    else                     onChange([...(value || []), id]);
  };
  return (
    <div>
      <div style={{ display: "flex", alignItems: "center", marginBottom: 4 }}>
        <label style={{ ...lbl, marginBottom: 0 }}>{lang === "fr" ? "Partenaires de mise en œuvre" : "Implementation partners"}</label>
        <div style={{ flex: 1 }} />
        {isAdmin && (
          <button type="button" className="btn xs ghost" onClick={() => setNewOpen(true)}
            style={{ fontSize: 10.5, padding: "2px 8px" }}>
            + {lang === "fr" ? "Nouveau" : "New"}
          </button>
        )}
      </div>
      <div style={{ maxHeight: 140, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6, padding: 6, background: "var(--bg-sunken)" }}>
        {(partners || []).length === 0 ? (
          <div className="text-faint" style={{ fontSize: 11, padding: 6, textAlign: "center" }}>
            {lang === "fr" ? "Aucun partenaire enregistré. Cliquer « + Nouveau » pour en ajouter." : "No partners yet. Click '+ New' to add one."}
          </div>
        ) : (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
            {(partners || []).map((p) => {
              const on = selectedSet.has(p.id);
              return (
                <button key={p.id} type="button" onClick={() => toggle(p.id)}
                  className={"pill" + (on ? " accent" : "")}
                  style={{
                    cursor: "pointer", fontSize: 11.5,
                    background: on ? "var(--accent, #4f46e5)" : "white",
                    color:      on ? "white" : "var(--text)",
                    border: "1px solid " + (on ? "var(--accent, #4f46e5)" : "var(--line)"),
                  }}>
                  {on && "✓ "}{lang === "fr" ? p.name_fr : (p.name_en || p.name_fr)}
                </button>
              );
            })}
          </div>
        )}
      </div>
      {newOpen && (
        <NewPartnerModal lang={lang}
          onClose={() => setNewOpen(false)}
          onCreated={async (p) => {
            // Refresh BEFORE updating the selection so the new partner pill
            // is rendered (otherwise the id is "selected" but no chip shows
            // until realtime fires, and the user thinks the entry was lost).
            await refresh();
            onChange([...(value || []), p.id]);
            setNewOpen(false);
          }} />
      )}
    </div>
  );
}

function IndicatorsMultiPicker({ lang, projectId, value, onChange, inp, lbl }) {
  // Pull refresh() so the user can force a re-fetch without closing the
  // modal — useful when an indicator is added in "Suivi des indicateurs"
  // in a parallel tab or when realtime isn't published for `indicators`.
  const { data: indicators, refresh: refreshIndicators } = window.melr.useIndicators(projectId);
  const selectedSet = new Set(value || []);
  const toggle = (id) => {
    if (selectedSet.has(id)) onChange(value.filter((x) => x !== id));
    else                     onChange([...(value || []), id]);
  };
  // Local search + "only selected" toggle. Useful when a project has dozens
  // of indicators — the user can narrow the visible list by code/name or
  // restrict to what they've already ticked to review their picks.
  const [search, setSearch] = useStateA("");
  const [onlySelected, setOnlySelected] = useStateA(false);
  const filtered = (indicators || []).filter((i) => {
    if (onlySelected && !selectedSet.has(i.id)) return false;
    if (!search.trim()) return true;
    const q = search.toLowerCase();
    const hay = `${i.code || ""} ${i.name_fr || ""} ${i.name_en || ""}`.toLowerCase();
    return hay.includes(q);
  });
  return (
    <div>
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
        <label style={{ ...lbl, marginBottom: 0 }}>{lang === "fr" ? "Indicateurs liés" : "Linked indicators"}</label>
        <span className="text-faint" style={{ fontSize: 10.5, fontWeight: 400 }}>
          {selectedSet.size} / {(indicators || []).length} {lang === "fr" ? "sélectionné(s)" : "selected"}
        </span>
      </div>
      {projectId && (indicators || []).length > 0 && (
        <div style={{ display: "flex", gap: 6, marginBottom: 4 }}>
          <input type="search" value={search} onChange={(e) => setSearch(e.target.value)}
            placeholder={lang === "fr" ? "Rechercher par code ou nom…" : "Search by code or name…"}
            style={{ ...inp, fontSize: 12, padding: "5px 8px" }} />
          <button type="button"
            className={"btn xs" + (onlySelected ? " primary" : " ghost")}
            onClick={() => setOnlySelected((v) => !v)}
            style={{ whiteSpace: "nowrap" }}
            title={lang === "fr" ? "N'afficher que les indicateurs sélectionnés" : "Show only selected indicators"}>
            {onlySelected
              ? (lang === "fr" ? "✓ Sélectionnés" : "✓ Selected")
              : (lang === "fr" ? "Sélectionnés" : "Selected")}
          </button>
          <button type="button" className="btn xs ghost"
            onClick={() => refreshIndicators && refreshIndicators()}
            title={lang === "fr" ? "Recharger la liste depuis le serveur (si tu viens d'ajouter un indicateur dans Suivi des indicateurs)" : "Reload the list from the server (if you just added an indicator in Indicator tracking)"}>
            🔄
          </button>
        </div>
      )}
      <div style={{ maxHeight: 200, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6, padding: 6, background: "var(--bg-sunken)" }}>
        {!projectId ? (
          <div className="text-faint" style={{ fontSize: 11, padding: 6, textAlign: "center" }}>
            {lang === "fr" ? "Choisir un projet d'abord pour voir ses indicateurs." : "Pick a project first to see its indicators."}
          </div>
        ) : (indicators || []).length === 0 ? (
          <div className="text-faint" style={{ fontSize: 11, padding: 6, textAlign: "center" }}>
            {lang === "fr" ? "Aucun indicateur pour ce projet." : "No indicators for this project yet."}
          </div>
        ) : filtered.length === 0 ? (
          <div className="text-faint" style={{ fontSize: 11, padding: 6, textAlign: "center" }}>
            {lang === "fr"
              ? `Aucun indicateur ne correspond à « ${search} ».`
              : `No indicators match "${search}".`}
          </div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", gap: 3 }}>
            {filtered.map((i) => {
              const on = selectedSet.has(i.id);
              return (
                <label key={i.id} style={{ display: "flex", gap: 6, alignItems: "center", padding: "3px 6px", cursor: "pointer", borderRadius: 4, background: on ? "color-mix(in oklch, var(--accent) 12%, transparent)" : "transparent" }}>
                  <input type="checkbox" checked={on} onChange={() => toggle(i.id)} />
                  <span className="mono" style={{ fontSize: 10.5, color: "var(--text-faint)", minWidth: 60 }}>{i.code}</span>
                  <span style={{ fontSize: 12 }}>{lang === "fr" ? i.name_fr : (i.name_en || i.name_fr)}</span>
                </label>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ============================================================================
// ATTACHMENTS SECTION · upload + list per activity
// ============================================================================
function ActivityAttachmentsSection({ activityId, lang }) {
  const { data: attachments, refresh } = window.melr.useActivityAttachments(activityId);
  const { profile } = window.melr.useCurrentProfile();
  const [uploadBusy, setUploadBusy] = useStateA(false);
  const [kind, setKind] = useStateA("");
  const [err, setErr] = useStateA(null);

  const onPick = async (e) => {
    const files = Array.from(e.target.files || []);
    if (files.length === 0) return;
    setUploadBusy(true); setErr(null);
    try {
      const orgId = profile && profile.organization_id;
      if (!orgId) throw new Error("No org context");
      for (const f of files) {
        await window.melr.uploadActivityAttachment(activityId, orgId, f, kind || null);
      }
      await refresh();
      e.target.value = "";
    } catch (e2) { setErr(e2.message); }
    finally { setUploadBusy(false); }
  };

  const onRemove = async (id) => {
    if (!window.confirm(lang === "fr" ? "Supprimer ce fichier ?" : "Delete this file?")) return;
    try {
      await window.melr.removeActivityAttachment(id);
      await refresh();
    } catch (e) { setErr(e.message); }
  };

  const openFile = async (path) => {
    const url = await window.melr.getActivityAttachmentUrl(path);
    if (url) window.open(url, "_blank");
  };

  return (
    <div>
      <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 6 }}>
        <select value={kind} onChange={(e) => setKind(e.target.value)}
          style={{ padding: "5px 8px", borderRadius: 5, border: "1px solid var(--line)", fontSize: 12, background: "var(--input-bg, var(--bg, white))" }}>
          <option value="">{lang === "fr" ? "— Type (optionnel) —" : "— Kind (optional) —"}</option>
          <option value="tdrs">TDRs</option>
          <option value="attendance">{lang === "fr" ? "Feuille de présence" : "Attendance sheet"}</option>
          <option value="photo">{lang === "fr" ? "Photo" : "Photo"}</option>
          <option value="report">{lang === "fr" ? "Rapport" : "Report"}</option>
          <option value="press">{lang === "fr" ? "Presse" : "Press"}</option>
          <option value="other">{lang === "fr" ? "Autre" : "Other"}</option>
        </select>
        <label className="btn sm" style={{ cursor: uploadBusy ? "wait" : "pointer" }}>
          <Icon.upload /> {uploadBusy ? "…" : (lang === "fr" ? "Téléverser…" : "Upload…")}
          <input type="file" multiple style={{ display: "none" }} onChange={onPick} disabled={uploadBusy} />
        </label>
      </div>
      {err && <div style={{ padding: "6px 10px", background: "#fee2e2", color: "#991b1b", borderRadius: 5, fontSize: 11.5, marginBottom: 6 }}>{err}</div>}
      {(attachments || []).length === 0 ? (
        <div className="text-faint" style={{ fontSize: 11, padding: 10, textAlign: "center", background: "var(--bg-sunken)", borderRadius: 6 }}>
          {lang === "fr" ? "Aucune pièce jointe." : "No attachments."}
        </div>
      ) : (
        <div style={{ display: "grid", gap: 4 }}>
          {(attachments || []).map((a) => (
            <div key={a.id} style={{ display: "flex", gap: 8, alignItems: "center", padding: "6px 10px", background: "var(--bg-sunken)", borderRadius: 5, fontSize: 12 }}>
              <button type="button" onClick={() => openFile(a.file_path)} className="link"
                style={{ background: "none", border: 0, padding: 0, color: "var(--accent, #4f46e5)", cursor: "pointer", fontSize: 12, textAlign: "left", flex: 1 }}>
                🗎 {a.file_name}
              </button>
              {a.kind && <span className="pill" style={{ fontSize: 10 }}>{a.kind}</span>}
              <span className="text-faint" style={{ fontSize: 10.5 }}>
                {a.size_bytes ? (a.size_bytes < 1024 * 1024 ? `${Math.round(a.size_bytes / 1024)} KB` : `${(a.size_bytes / 1024 / 1024).toFixed(1)} MB`) : ""}
              </span>
              <button type="button" className="btn xs ghost" onClick={() => onRemove(a.id)}
                title={lang === "fr" ? "Supprimer" : "Delete"}>
                <Icon.x />
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ============================================================================
// INLINE CREATE-NEW MODALS for each lookup
// ============================================================================

function NewActivityTypeModal({ lang, onClose, onCreated }) {
  const [code, setCode]       = useStateA("");
  const [nameFr, setNameFr]   = useStateA("");
  const [nameEn, setNameEn]   = useStateA("");
  const [busy, setBusy]       = useStateA(false);
  const [err, setErr]         = useStateA(null);
  const submit = async (e) => {
    e.preventDefault();
    setBusy(true); setErr(null);
    try {
      const created = await window.melr.activityTypesCrud.create({
        code: code.trim() || nameFr.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 32),
        name_fr: nameFr.trim(),
        name_en: nameEn.trim() || null,
      });
      onCreated(created);
    } catch (e2) { setErr(e2.message); }
    finally { setBusy(false); }
  };
  return <SmallLookupModal lang={lang} title={lang === "fr" ? "Nouveau type d'activité" : "New activity type"}
    onClose={onClose} onSubmit={submit} busy={busy} err={err}
    fields={[
      { label: "Code", value: code,   setValue: setCode,   placeholder: "formation_avancee" },
      { label: lang === "fr" ? "Nom (FR) *" : "Name (FR) *", value: nameFr, setValue: setNameFr, required: true, placeholder: lang === "fr" ? "Formation avancée" : "Advanced training" },
      { label: lang === "fr" ? "Nom (EN)" : "Name (EN)",     value: nameEn, setValue: setNameEn, placeholder: "Advanced training" },
    ]} />;
}

function NewPartnerModal({ lang, onClose, onCreated }) {
  const [nameFr, setNameFr]   = useStateA("");
  const [nameEn, setNameEn]   = useStateA("");
  const [code, setCode]       = useStateA("");
  const [busy, setBusy]       = useStateA(false);
  const [err, setErr]         = useStateA(null);
  const submit = async (e) => {
    e.preventDefault();
    setBusy(true); setErr(null);
    try {
      const created = await window.melr.partnersCrud.create({
        code: code.trim() || null,
        name_fr: nameFr.trim(),
        name_en: nameEn.trim() || null,
      });
      onCreated(created);
    } catch (e2) { setErr(e2.message); }
    finally { setBusy(false); }
  };
  return <SmallLookupModal lang={lang} title={lang === "fr" ? "Nouveau partenaire" : "New partner"}
    onClose={onClose} onSubmit={submit} busy={busy} err={err}
    fields={[
      { label: lang === "fr" ? "Nom (FR) *" : "Name (FR) *", value: nameFr, setValue: setNameFr, required: true },
      { label: lang === "fr" ? "Nom (EN)" : "Name (EN)",     value: nameEn, setValue: setNameEn },
      { label: "Code", value: code, setValue: setCode, placeholder: "UNICEF, USAID…" },
    ]} />;
}

function NewProjectComponentModal({ lang, projectId, onClose, onCreated }) {
  const [code, setCode]       = useStateA("");
  const [nameFr, setNameFr]   = useStateA("");
  const [nameEn, setNameEn]   = useStateA("");
  const [busy, setBusy]       = useStateA(false);
  const [err, setErr]         = useStateA(null);
  const submit = async (e) => {
    e.preventDefault();
    setBusy(true); setErr(null);
    try {
      const created = await window.melr.projectComponentsCrud.create({
        project_id: projectId,
        code: code.trim() || null,
        name_fr: nameFr.trim(),
        name_en: nameEn.trim() || null,
      });
      onCreated(created);
    } catch (e2) { setErr(e2.message); }
    finally { setBusy(false); }
  };
  return <SmallLookupModal lang={lang} title={lang === "fr" ? "Nouvelle composante projet" : "New project component"}
    onClose={onClose} onSubmit={submit} busy={busy} err={err}
    fields={[
      { label: lang === "fr" ? "Nom (FR) *" : "Name (FR) *", value: nameFr, setValue: setNameFr, required: true,
        placeholder: lang === "fr" ? "Composante Santé" : "Health component" },
      { label: lang === "fr" ? "Nom (EN)" : "Name (EN)",     value: nameEn, setValue: setNameEn },
      { label: "Code", value: code, setValue: setCode, placeholder: "C1, C2…" },
    ]} />;
}

// Tiny shared modal shell to avoid copy-pasting the same wrapping JSX 3 times.
// Note: this modal is OPENED FROM INSIDE the ActivityEditorModal's <form>.
// We MUST NOT render another <form> inside it (HTML forbids nesting forms,
// and the inner submit otherwise bubbles to the parent form → double-submit
// of the parent activity with stale values → page blanche).
// Solution: pass onSubmit=null to Modal so its wrapper stays a <div>, and
// trigger the create via onClick on the "Créer" button.
function SmallLookupModal({ lang, title, onClose, onSubmit, busy, err, fields }) {
  const inp = { width: "100%", padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--input-bg, var(--bg, white))", color: "var(--text)", boxSizing: "border-box", fontFamily: "inherit" };
  const lbl = { display: "block", fontSize: 11, color: "var(--text-faint)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.04em" };
  // Enter-to-submit on the last input is preserved by listening at the
  // container level — we synthesise a "submit-like" event for the caller
  // who still expects { preventDefault() }.
  const fakeSubmit = () => onSubmit({ preventDefault: () => {} });
  const onKeyDown = (e) => {
    if (e.key === "Enter" && !busy && !e.shiftKey) {
      // Only intercept Enter inside <input> — keep <textarea> linebreaks
      // working as expected.
      if (e.target && e.target.tagName === "INPUT") {
        e.preventDefault();
        e.stopPropagation();
        fakeSubmit();
      }
    }
  };
  return (
    <Modal size="sm" title={title} onClose={onClose}
      footer={<>
        <button type="button" className="btn sm ghost" onClick={onClose} disabled={busy}>
          {lang === "fr" ? "Annuler" : "Cancel"}
        </button>
        <button type="button" className="btn sm primary" disabled={busy} onClick={fakeSubmit}>
          {busy ? "…" : (lang === "fr" ? "Créer" : "Create")}
        </button>
      </>}>
      <div style={{ display: "grid", gap: 10 }} onKeyDown={onKeyDown}>
        {fields.map((f, i) => (
          <div key={i}>
            <label style={lbl}>{f.label}</label>
            <input style={inp} value={f.value} onChange={(e) => f.setValue(e.target.value)}
              placeholder={f.placeholder || ""} required={!!f.required} />
          </div>
        ))}
        {err && (
          <div style={{ padding: "6px 10px", background: "#fee2e2", color: "#991b1b", borderRadius: 5, fontSize: 12 }}>{err}</div>
        )}
      </div>
    </Modal>
  );
}

// Expose for app.jsx route dispatch.
window.Activities = Activities;
