import { fieldsByKindConfig } from '../../config/fields-config.js';
import { $, escapeHtml } from '../../core/dom.js';
import { api } from '../../core/api.js';
import { normalizeUiTree } from '../../core/text.js';
import { refreshCurrentWorkView } from '../../services/batch.js';
import { buildFieldSelectionKey, getOrderedFieldEntries, isAlwaysSelectedDocType, isFieldDefaultSelected } from '../../services/field-selections.js';
import { markFieldSelectionsDirty } from '../../services/preferences.js';
import { _getEffectiveFieldConfig, getCurrentProjectKind, normalizeSearchText, resolveFieldLabel } from '../../services/project-context.js';
import { settingsUiState, state, subProfilePanelState, subProjectEditorState, runtime } from '../../state/app-state.js';
import { _applySubProfileFieldSearchFilter, _buildDefaultFieldsFromConfig, _clearProfileCiImportUi, _editorDocTypes, _editorDoctypeFields, _editorFields, _editorKeyingInstructions, _profilePanelUpdateEditChrome, _renderProfilePanel, _setCiImportStatus, _setEditorDocTypes, _setEditorDoctypeFields, _setEditorFields, _setEditorKeyingInstructions, _setProfileCiImportStatus, cancelProfilePanelThresholdEdit, getAssignableSubProjects, moveFieldOrderBefore, normalizeFieldsKindTab, setActiveFieldsKindTab, setActiveSettingsTab } from '../events.js';
import { setStatus } from '../status-bar.js';
import { syncFontSizeSettingsUi } from '../theme.js';

export function closeSubProjectAssignPanel() {
  runtime.subProjectAssignSelection = null;
  const panel = $("subProjectAssignPanel");
  const backdrop = $("subProjectAssignBackdrop");
  if (panel) panel.hidden = true;
  if (backdrop) backdrop.hidden = true;
}


export function openSubProjectAssignPanel(project) {
  const panel = $("subProjectAssignPanel");
  const backdrop = $("subProjectAssignBackdrop");
  const titleEl = $("subProjectAssignTitle");
  const msgEl = $("subProjectAssignMessage");
  const selectEl = $("subProjectAssignSelect");
  if (!panel || !backdrop || !titleEl || !msgEl || !selectEl) return;

  const lotName = String((project && project.project_name) || "").trim();
  const rootKind = String((project && project.kind) || "").trim();
  const currentSubProject = String((project && project.sub_project) || "").trim();
  const options = getAssignableSubProjects(rootKind);
  if (!lotName) {
    setStatus("Lot introuvable.", true);
    return;
  }
  if (!options.length) {
    setStatus("Aucun sous-projet disponible pour ce type.", true);
    return;
  }

  runtime.subProjectAssignSelection = { lotName };
  titleEl.textContent = "Changer le sous-projet";
  msgEl.textContent = `Lot "${lotName}"`;
  selectEl.innerHTML = "";
  options.forEach((name) => {
    const opt = document.createElement("option");
    opt.value = name;
    opt.textContent = name;
    selectEl.appendChild(opt);
  });
  if (currentSubProject && options.includes(currentSubProject)) {
    selectEl.value = currentSubProject;
  } else {
    selectEl.selectedIndex = 0;
  }

  panel.hidden = false;
  backdrop.hidden = false;
  selectEl.focus();
}


export function syncShortcutsStartupOptionUi() {
  const opt = $("optShowShortcutsOnStartup");
  if (opt) opt.checked = !!state.showShortcutsOnStartup;
}


export function toggleSettingsPanel(open) {
  const panel = $("settingsPanel");
  if (!panel) return;
  const mainLayout = $("mainLayout");
  state.settingsOpen = !!open;
  panel.hidden = !state.settingsOpen;
  if (mainLayout) mainLayout.hidden = state.settingsOpen;
  document.body.classList.toggle("settings-page-open", state.settingsOpen);
  const btn = $("menuSettingsOpen");
  if (btn) btn.setAttribute("aria-expanded", state.settingsOpen ? "true" : "false");
}


export function closeSettingsPanel(reason = "manual") {
  toggleSettingsPanel(false);
}


export async function openSettingsPanel() {
  if (state.settingsOpen || settingsUiState.opening) return;
  settingsUiState.opening = true;
  toggleSettingsPanel(true);
  settingsUiState.lastOpenedAtMs = Date.now();
  syncFontSizeSettingsUi();
  try {
    // Ensure sub-project tabs are available before the first Champs render.
    const preloadTasks = [loadSubProjects()];
    if (!settingsUiState.projectKindsLoadedOnce) {
      if (!settingsUiState.projectKindsInFlight) {
        settingsUiState.projectKindsInFlight = loadProjectKinds().finally(() => {
          settingsUiState.projectKindsInFlight = null;
        });
      }
      preloadTasks.push(settingsUiState.projectKindsInFlight);
    }
    await Promise.all(preloadTasks);
    setActiveSettingsTab("fields");
  } finally {
    settingsUiState.opening = false;
  }
}


export function initSettingsTabs() {
  const tabs = Array.from(document.querySelectorAll(".settings-tab"));
  tabs.forEach((tab) => {
    tab.addEventListener("click", () => {
      setActiveSettingsTab(tab.dataset.settingsTab);
    });
  });
}


function formatModelPrice(value) {
  if (value == null || Number.isNaN(Number(value))) return "Non indiqué";
  const n = Number(value);
  if (n === 0) return "Gratuit";
  if (n < 0.001) return `$${n.toFixed(6)} / 1M tokens`;
  if (n < 1) return `$${n.toFixed(4)} / 1M tokens`;
  return `$${n.toFixed(2)} / 1M tokens`;
}


function formatCompactModelPrice(value) {
  return formatModelPrice(value).replace(" / 1M tokens", "");
}


function buildModelListPriceLabel(pricing) {
  const p = pricing || {};
  const parts = [
    `P ${formatCompactModelPrice(p.prompt_per_million)}`,
    `C ${formatCompactModelPrice(p.completion_per_million)}`,
  ];
  if (p.image_per_million != null) {
    parts.push(`Img ${formatCompactModelPrice(p.image_per_million)}`);
  }
  if (p.request_per_million != null) {
    parts.push(`Req ${formatCompactModelPrice(p.request_per_million)}`);
  }
  return parts.join(" | ");
}


function formatModelNumber(value, suffix) {
  if (typeof value !== "number" || !Number.isFinite(value)) return "Non indiqué";
  return `${value.toLocaleString()}${suffix || ""}`;
}


function modelSearchText(model) {
  return [
    model && model.name,
    model && model.id,
    model && model.description,
  ].map((v) => String(v || "").toLowerCase()).join(" ");
}


function filteredPageFillModels() {
  const query = String(settingsUiState.pageFillModelQuery || "").trim().toLowerCase();
  const models = Array.isArray(settingsUiState.pageFillModels) ? settingsUiState.pageFillModels : [];
  if (!query) return models;
  return models.filter((model) => modelSearchText(model).includes(query));
}


function selectedPageFillModel() {
  const selectedId = String(settingsUiState.pageFillSelectedModel || "").trim();
  return (settingsUiState.pageFillModels || []).find((model) => model.id === selectedId) || null;
}


function selectedPageFillProvider() {
  return String(settingsUiState.pageFillSelectedProvider || "").trim();
}


function formatProviderListLabel(provider) {
  const pricing = provider && provider.pricing ? provider.pricing : {};
  const price = buildModelListPriceLabel(pricing);
  const count = Number(provider && provider.endpoint_count);
  const suffix = Number.isFinite(count) && count > 1 ? ` (${count} endpoints)` : "";
  return `${(provider && (provider.name || provider.id)) || "Fournisseur"}${suffix}  -  ${price}`;
}


function renderAiModelDetails(host, model) {
  if (!host) return;
  if (!model) {
    host.innerHTML = `<p class="muted">Sélectionnez un modèle pour voir ses détails.</p>`;
    return;
  }
  const pricing = model.pricing || {};
  const description = String(model.description || "").trim();
  host.innerHTML = `
    <div class="ai-model-details__eyebrow">Modèle sélectionné</div>
    <h3>${escapeHtml(model.name || model.id)}</h3>
    <p class="ai-model-details__id">${escapeHtml(model.id)}</p>
    ${description ? `<p class="ai-model-details__description">${escapeHtml(description)}</p>` : ""}
    <div class="ai-model-details__grid">
      <div><span>Input</span><strong>${escapeHtml((model.input_modalities || []).join(", ") || "image")}</strong></div>
      <div><span>Output</span><strong>${escapeHtml((model.output_modalities || []).join(", ") || "text")}</strong></div>
      <div><span>Contexte</span><strong>${formatModelNumber(model.context_length, " tokens")}</strong></div>
      <div><span>Max output</span><strong>${formatModelNumber(model.max_output, " tokens")}</strong></div>
      <div><span>Prompt</span><strong>${formatModelPrice(pricing.prompt_per_million)}</strong></div>
      <div><span>Completion</span><strong>${formatModelPrice(pricing.completion_per_million)}</strong></div>
      <div><span>Image</span><strong>${formatModelPrice(pricing.image_per_million)}</strong></div>
      <div><span>Requête</span><strong>${formatModelPrice(pricing.request_per_million)}</strong></div>
    </div>
  `;
}


function renderPageFillModelDetails() {
  renderAiModelDetails($("pageFillModelDetails"), selectedPageFillModel());
}


function syncPageFillModelSaveState() {
  const btn = $("btnSavePageFillModel");
  if (!btn) return;
  const selected = String(settingsUiState.pageFillSelectedModel || "").trim();
  const saved = String(settingsUiState.pageFillSavedModel || "").trim();
  const selectedProvider = selectedPageFillProvider();
  const savedProvider = String(settingsUiState.pageFillSavedProvider || "").trim();
  btn.disabled = !selected || (selected === saved && selectedProvider === savedProvider);
}


export function renderPageFillProviders() {
  const select = $("pageFillProviderSelect");
  if (!select) return;
  const providers = Array.isArray(settingsUiState.pageFillProviders) ? settingsUiState.pageFillProviders : [];
  select.innerHTML = "";

  const automatic = document.createElement("option");
  automatic.value = "";
  automatic.textContent = "Automatique";
  select.appendChild(automatic);

  providers.forEach((provider) => {
    const opt = document.createElement("option");
    opt.value = provider.id || provider.name || "";
    opt.textContent = formatProviderListLabel(provider);
    opt.title = provider.tag ? `${provider.name || provider.id} - ${provider.tag}` : `${provider.name || provider.id}`;
    select.appendChild(opt);
  });

  select.value = selectedPageFillProvider();
  if (select.value !== selectedPageFillProvider()) {
    settingsUiState.pageFillSelectedProvider = "";
    select.value = "";
  }
  select.disabled = !!settingsUiState.pageFillProvidersInFlight || !settingsUiState.pageFillSelectedModel;
  syncPageFillModelSaveState();
}


export async function loadPageFillProviders(modelId, force = false) {
  const model = String(modelId || "").trim();
  if (!model) {
    settingsUiState.pageFillProviders = [];
    settingsUiState.pageFillProvidersLoadedForModel = "";
    settingsUiState.pageFillSelectedProvider = "";
    renderPageFillProviders();
    return;
  }
  if (settingsUiState.pageFillProvidersLoadedForModel === model && !force) {
    renderPageFillProviders();
    return;
  }
  if (settingsUiState.pageFillProvidersInFlight) {
    await settingsUiState.pageFillProvidersInFlight;
    renderPageFillProviders();
    return;
  }
  const status = $("pageFillProviderStatus");
  if (status) status.textContent = "Chargement des fournisseurs...";
  settingsUiState.pageFillProvidersInFlight = api(`/api/ai/page-fill/model-providers?model=${encodeURIComponent(model)}`)
    .then((data) => {
      settingsUiState.pageFillProviders = Array.isArray(data.providers) ? data.providers : [];
      settingsUiState.pageFillProvidersLoadedForModel = model;
      const autoProvider = String(data.selected_provider || "").trim();
      if (autoProvider) {
        settingsUiState.pageFillSelectedProvider = autoProvider;
      } else if (settingsUiState.pageFillSavedModel === model && !settingsUiState.pageFillSelectedProvider) {
        settingsUiState.pageFillSelectedProvider = String(settingsUiState.pageFillSavedProvider || "").trim();
      }
      if (status) {
        status.textContent = settingsUiState.pageFillProviders.length
          ? `${settingsUiState.pageFillProviders.length} fournisseur(s).`
          : "Aucun fournisseur spécifique.";
      }
    })
    .catch((e) => {
      settingsUiState.pageFillProviders = [];
      settingsUiState.pageFillProvidersLoadedForModel = "";
      if (status) status.textContent = e && e.message ? e.message : "Impossible de charger les fournisseurs.";
    })
    .finally(() => {
      settingsUiState.pageFillProvidersInFlight = null;
    });
  await settingsUiState.pageFillProvidersInFlight;
  renderPageFillProviders();
}


export function renderPageFillModels() {
  const select = $("pageFillModelSelect");
  if (!select) return;
  const models = filteredPageFillModels();
  select.innerHTML = "";
  models.forEach((model) => {
    const opt = document.createElement("option");
    opt.value = model.id;
    const pricing = model.pricing || {};
    const price = buildModelListPriceLabel(pricing);
    opt.textContent = `${model.name || model.id}  -  ${price}`;
    opt.title = `${model.id} - ${price} / 1M tokens`;
    select.appendChild(opt);
  });
  if (settingsUiState.pageFillSelectedModel) {
    select.value = settingsUiState.pageFillSelectedModel;
  }
  if (models.length && !select.value) {
    select.selectedIndex = 0;
    settingsUiState.pageFillSelectedModel = select.value;
  }
  renderPageFillModelDetails();
  renderPageFillProviders();
  syncPageFillModelSaveState();
}


export async function loadPageFillModels(force = false) {
  if (settingsUiState.pageFillModelsLoadedOnce && !force) {
    renderPageFillModels();
    return;
  }
  if (settingsUiState.pageFillModelsInFlight) {
    await settingsUiState.pageFillModelsInFlight;
    renderPageFillModels();
    return;
  }
  const status = $("pageFillModelStatus");
  if (status) status.textContent = "Chargement des modèles...";
  settingsUiState.pageFillModelsInFlight = api("/api/ai/page-fill/models")
    .then((data) => {
      settingsUiState.pageFillModels = Array.isArray(data.models) ? data.models : [];
      settingsUiState.pageFillSavedModel = String(data.selected_model || data.default_model || "").trim();
      settingsUiState.pageFillSelectedModel = settingsUiState.pageFillSavedModel;
      settingsUiState.pageFillSavedProvider = String(data.selected_provider || "").trim();
      settingsUiState.pageFillSelectedProvider = settingsUiState.pageFillSavedProvider;
      settingsUiState.pageFillModelsLoadedOnce = true;
      if (status) status.textContent = `${settingsUiState.pageFillModels.length} modèles image vers texte.`;
    })
    .catch((e) => {
      settingsUiState.pageFillModels = [];
      if (status) status.textContent = e && e.message ? e.message : "Impossible de charger les modèles.";
    })
    .finally(() => {
      settingsUiState.pageFillModelsInFlight = null;
    });
  await settingsUiState.pageFillModelsInFlight;
  renderPageFillModels();
  await loadPageFillProviders(settingsUiState.pageFillSelectedModel);
}


export function initPageFillModelSettings() {
  const search = $("pageFillModelSearch");
  if (search) {
    search.addEventListener("input", () => {
      settingsUiState.pageFillModelQuery = search.value || "";
      renderPageFillModels();
    });
  }
  const select = $("pageFillModelSelect");
  if (select) {
    select.addEventListener("change", () => {
      settingsUiState.pageFillSelectedModel = select.value || "";
      settingsUiState.pageFillSelectedProvider =
        settingsUiState.pageFillSelectedModel === settingsUiState.pageFillSavedModel
          ? settingsUiState.pageFillSavedProvider
          : "";
      settingsUiState.pageFillProvidersLoadedForModel = "";
      settingsUiState.pageFillProviders = [];
      renderPageFillModelDetails();
      renderPageFillProviders();
      void loadPageFillProviders(settingsUiState.pageFillSelectedModel);
      syncPageFillModelSaveState();
    });
  }
  const providerSelect = $("pageFillProviderSelect");
  if (providerSelect) {
    providerSelect.addEventListener("change", () => {
      settingsUiState.pageFillSelectedProvider = providerSelect.value || "";
      syncPageFillModelSaveState();
    });
  }
  const refresh = $("btnRefreshPageFillModels");
  if (refresh) {
    refresh.addEventListener("click", async () => {
      await loadPageFillModels(true);
    });
  }
  const save = $("btnSavePageFillModel");
  if (save) {
    save.addEventListener("click", async () => {
      const model = String(settingsUiState.pageFillSelectedModel || "").trim();
      const provider = selectedPageFillProvider();
      if (!model) return;
      const status = $("pageFillModelStatus");
      save.disabled = true;
      if (status) status.textContent = "Enregistrement...";
      try {
        const data = await api("/api/ai/page-fill/model", {
          method: "POST",
          body: JSON.stringify({ model, provider }),
        });
        settingsUiState.pageFillSavedModel = String(data.model || model).trim();
        settingsUiState.pageFillSavedProvider = String(data.provider || provider).trim();
        settingsUiState.pageFillSelectedProvider = settingsUiState.pageFillSavedProvider;
        if (status) status.textContent = "Modèle enregistré.";
        setStatus("Modèle AI Page Fill enregistré.");
      } catch (e) {
        if (status) status.textContent = e && e.message ? e.message : "Erreur d'enregistrement.";
      } finally {
        syncPageFillModelSaveState();
      }
    });
  }
}


function filteredAutocorrectModels() {
  const query = String(settingsUiState.autocorrectModelQuery || "").trim().toLowerCase();
  const models = Array.isArray(settingsUiState.autocorrectModels) ? settingsUiState.autocorrectModels : [];
  if (!query) return models;
  return models.filter((model) => modelSearchText(model).includes(query));
}


function selectedAutocorrectModel() {
  const selectedId = String(settingsUiState.autocorrectSelectedModel || "").trim();
  return (settingsUiState.autocorrectModels || []).find((model) => model.id === selectedId) || null;
}


function renderAutocorrectModelDetails() {
  renderAiModelDetails($("autocorrectModelDetails"), selectedAutocorrectModel());
}


function syncAutocorrectModelSaveState() {
  const btn = $("btnSaveAutocorrectModel");
  if (!btn) return;
  const selected = String(settingsUiState.autocorrectSelectedModel || "").trim();
  const saved = String(settingsUiState.autocorrectSavedModel || "").trim();
  btn.disabled = !selected || selected === saved;
}


export function renderAutocorrectModels() {
  const select = $("autocorrectModelSelect");
  if (!select) return;
  const models = filteredAutocorrectModels();
  select.innerHTML = "";
  models.forEach((model) => {
    const opt = document.createElement("option");
    opt.value = model.id;
    const pricing = model.pricing || {};
    const price = buildModelListPriceLabel(pricing);
    opt.textContent = `${model.name || model.id}  -  ${price}`;
    opt.title = `${model.id} - ${price} / 1M tokens`;
    select.appendChild(opt);
  });
  if (settingsUiState.autocorrectSelectedModel) {
    select.value = settingsUiState.autocorrectSelectedModel;
  }
  if (models.length && !select.value) {
    select.selectedIndex = 0;
    settingsUiState.autocorrectSelectedModel = select.value;
  }
  renderAutocorrectModelDetails();
  syncAutocorrectModelSaveState();
}


export async function loadAutocorrectModels(force = false) {
  if (settingsUiState.autocorrectModelsLoadedOnce && !force) {
    renderAutocorrectModels();
    return;
  }
  if (settingsUiState.autocorrectModelsInFlight) {
    await settingsUiState.autocorrectModelsInFlight;
    renderAutocorrectModels();
    return;
  }
  const status = $("autocorrectModelStatus");
  if (status) status.textContent = "Chargement des modèles...";
  settingsUiState.autocorrectModelsInFlight = api("/api/ai/autocorrect/models")
    .then((data) => {
      settingsUiState.autocorrectModels = Array.isArray(data.models) ? data.models : [];
      settingsUiState.autocorrectSavedModel = String(data.selected_model || data.default_model || "").trim();
      settingsUiState.autocorrectSelectedModel = settingsUiState.autocorrectSavedModel;
      settingsUiState.autocorrectModelsLoadedOnce = true;
      if (status) status.textContent = `${settingsUiState.autocorrectModels.length} modèles image vers texte.`;
    })
    .catch((e) => {
      settingsUiState.autocorrectModels = [];
      if (status) status.textContent = e && e.message ? e.message : "Impossible de charger les modèles.";
    })
    .finally(() => {
      settingsUiState.autocorrectModelsInFlight = null;
    });
  await settingsUiState.autocorrectModelsInFlight;
  renderAutocorrectModels();
}


export function initAutocorrectModelSettings() {
  const search = $("autocorrectModelSearch");
  if (search) {
    search.addEventListener("input", () => {
      settingsUiState.autocorrectModelQuery = search.value || "";
      renderAutocorrectModels();
    });
  }
  const select = $("autocorrectModelSelect");
  if (select) {
    select.addEventListener("change", () => {
      settingsUiState.autocorrectSelectedModel = select.value || "";
      renderAutocorrectModelDetails();
      syncAutocorrectModelSaveState();
    });
  }
  const refresh = $("btnRefreshAutocorrectModels");
  if (refresh) {
    refresh.addEventListener("click", async () => {
      await loadAutocorrectModels(true);
    });
  }
  const save = $("btnSaveAutocorrectModel");
  if (save) {
    save.addEventListener("click", async () => {
      const model = String(settingsUiState.autocorrectSelectedModel || "").trim();
      if (!model) return;
      const status = $("autocorrectModelStatus");
      save.disabled = true;
      if (status) status.textContent = "Enregistrement...";
      try {
        const data = await api("/api/ai/autocorrect/model", {
          method: "POST",
          body: JSON.stringify({ model }),
        });
        settingsUiState.autocorrectSavedModel = String(data.model || model).trim();
        if (status) status.textContent = "Modèle enregistré.";
        setStatus("Modèle auto-correction AI enregistré.");
      } catch (e) {
        if (status) status.textContent = e && e.message ? e.message : "Erreur d'enregistrement.";
      } finally {
        syncAutocorrectModelSaveState();
      }
    });
  }
}


export function renderFieldsKindTabs() {
  const container = $("fieldsKindTabsContainer");
  if (!container) return;
  container.innerHTML = "";
  const tabs =
    state.subProjects && state.subProjects.length
      ? state.subProjects.map((sp) => ({ id: sp.name, label: sp.name }))
      : [
          { id: "Census", label: "Census" },
          { id: "BDM", label: "BDM" },
        ];

  tabs.forEach(({ id, label }) => {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.className = "fields-kind-tab";
    btn.dataset.kindTab = id;
    btn.setAttribute("role", "tab");
    btn.setAttribute("aria-selected", "false");
    btn.textContent = label;
    btn.addEventListener("click", () => setActiveFieldsKindTab(id));
    container.appendChild(btn);
  });
  // Re-activate the correct tab after rebuilding
  setActiveFieldsKindTab(state.selectedFieldsKind);
}


export function syncFieldsKindTabToCurrentProject() {
  setActiveFieldsKindTab(getCurrentProjectKind());
}


export function initFieldsKindTabs() {
  // Tabs are generated dynamically; renderFieldsKindTabs() wires the listeners.
}


export function renderFieldsByKind(kind) {
  const host = $("fieldsByKindContainer");
  if (!host) return;
  const config = _getEffectiveFieldConfig(kind);
  const docTypes = Object.keys(config);
  const normalizedQuery = normalizeSearchText(state.fieldSearchQuery);
  host.innerHTML = "";

  if (!docTypes.length) {
    const empty = document.createElement("div");
    empty.className = "fields-empty";
    empty.textContent = "Aucun champ configuré pour ce type de projet.";
    host.appendChild(empty);
    return;
  }

  const grid = document.createElement("div");
  grid.className = "fields-doctype-grid";
  docTypes.forEach((docType) => {
    const card = document.createElement("section");
    card.className = "fields-doctype-card" + (docType === "__HEADER__" ? " fields-doctype-card--header" : "");

    const title = document.createElement("h3");
    title.className = "fields-doctype-title";
    if (docType === "__HEADER__") {
      title.textContent = "En-tête";
    } else {
      title.textContent = docType === "__GENERAL__" ? "Général (pour tous les EventType)" : docType;
    }
    const mixedEventTypePrefix = "Mixed \u2192 ";
    if (typeof docType === "string" && docType.startsWith(mixedEventTypePrefix)) {
      title.classList.add("fields-doctype-title--mixed-eventtype");
    }
    card.appendChild(title);

    const list = document.createElement("div");
    list.className = "fields-check-list";

    const fields = config[docType] || [];
    const orderedEntries = getOrderedFieldEntries(kind, docType, fields);
    const filteredEntries = !normalizedQuery
      ? orderedEntries
      : orderedEntries.filter((entry) => {
          const field = entry.field || {};
          const label = normalizeSearchText(field.fieldLabel || "");
          const name = normalizeSearchText(field.fieldName || "");
          return label.includes(normalizedQuery) || name.includes(normalizedQuery);
        });

    if (!filteredEntries.length) {
      const noMatch = document.createElement("div");
      noMatch.className = "fields-empty fields-empty--inline";
      noMatch.textContent = normalizedQuery ? "Aucun champ correspondant." : "Aucun champ configuré.";
      list.appendChild(noMatch);
    }

    filteredEntries.forEach((entry, index) => {
      const field = entry.field;
      const row = document.createElement("div");
      row.className = "fields-check fields-check--draggable";
      row.dataset.orderId = entry.id;
      row.addEventListener("dragenter", (e) => {
        e.preventDefault();
        row.classList.add("is-drop-target");
      });
      row.addEventListener("dragover", (e) => {
        e.preventDefault();
        e.dataTransfer.dropEffect = "move";
        row.classList.add("is-drop-target");
      });
      row.addEventListener("dragleave", () => {
        row.classList.remove("is-drop-target");
      });
      row.addEventListener("drop", async (e) => {
        e.preventDefault();
        row.classList.remove("is-drop-target");
        const fromId = e.dataTransfer.getData("text/plain");
        const didMove = moveFieldOrderBefore(kind, docType, fields, fromId, entry.id);
        if (!didMove) return;
        markFieldSelectionsDirty();
        renderFieldsByKind(kind);
        await refreshCurrentWorkView();
      });

      const dragHandle = document.createElement("span");
      dragHandle.className = "fields-check__drag";
      dragHandle.draggable = true;
      dragHandle.title = "Drag to reorder";
      dragHandle.setAttribute("aria-label", "Drag to reorder");
      dragHandle.addEventListener("dragstart", (e) => {
        e.dataTransfer.effectAllowed = "move";
        e.dataTransfer.setData("text/plain", entry.id);
        row.classList.add("is-dragging");
      });
      dragHandle.addEventListener("dragend", () => {
        row.classList.remove("is-dragging");
        row.classList.remove("is-drop-target");
      });

      const checkbox = document.createElement("input");
      checkbox.type = "checkbox";
      const alwaysSelected = isAlwaysSelectedDocType(docType);
      const selectionKey = buildFieldSelectionKey(kind, docType, field.fieldName || "");
      const displayLabel = resolveFieldLabel(
        field.fieldName || "",
        field.fieldLabel || field.fieldName || ""
      );
      if (!Object.prototype.hasOwnProperty.call(state.fieldSelections, selectionKey)) {
        state.fieldSelections[selectionKey] = isFieldDefaultSelected(docType, field);
      }
      checkbox.checked = !!state.fieldSelections[selectionKey];
      checkbox.dataset.kind = kind;
      checkbox.dataset.docType = docType;
      checkbox.dataset.fieldName = field.fieldName || "";
      checkbox.dataset.fieldLabel = displayLabel;
      checkbox.id = `field_${kind}_${docType}_${index}`;
      checkbox.addEventListener("change", async () => {
        state.fieldSelections[selectionKey] = checkbox.checked;
        markFieldSelectionsDirty();
        await refreshCurrentWorkView();
      });

      const labelText = document.createElement("label");
      labelText.className = "fields-check__label";
      labelText.textContent = displayLabel;
      labelText.dataset.fieldName = field.fieldName || "";
      labelText.setAttribute("for", checkbox.id);

      row.appendChild(dragHandle);
      row.appendChild(checkbox);
      row.appendChild(labelText);
      list.appendChild(row);
    });

    card.appendChild(list);
    grid.appendChild(card);
  });

  host.appendChild(grid);
  normalizeUiTree(host);
}


export function renderProjectKinds(projects, summary) {
  const tbody = $("projectKindsTableBody");
  if (!tbody) return;
  const detectedLotsCount = $("detectedLotsCount");
  if (detectedLotsCount) {
    const uniqueLots = new Set(
      (Array.isArray(projects) ? projects : [])
        .map((project) => String(project && project.project_name ? project.project_name : "").trim())
        .filter(Boolean)
    );
    detectedLotsCount.textContent = String(uniqueLots.size);
  }

  tbody.innerHTML = "";
  projects.forEach((project) => {
    const tr = document.createElement("tr");

    const nameTd = document.createElement("td");
    nameTd.textContent = project.project_name || "";
    tr.appendChild(nameTd);

    const kindTd = document.createElement("td");
    const badge = document.createElement("span");
    const isCensus = String(project.kind || "").toLowerCase() === "census";
    badge.className = "kind-badge " + (isCensus ? "kind-badge--census" : "kind-badge--bdm");
    badge.textContent = isCensus ? "Census" : "BDM";
    kindTd.appendChild(badge);
    tr.appendChild(kindTd);

    const subTd = document.createElement("td");
    subTd.className = "doc-types-list";
    const subContent = document.createElement("div");
    subContent.className = "kinds-subproject-cell";
    const sub =
      project.sub_project != null && String(project.sub_project).trim()
        ? String(project.sub_project).trim()
        : "";
    const subLabel = document.createElement("span");
    subLabel.className = "kinds-subproject-name";
    subLabel.textContent = sub || "-";
    subContent.appendChild(subLabel);
    if (sub) {
      const editBtn = document.createElement("button");
      editBtn.type = "button";
      editBtn.className = "btn btn--small btn--icon kinds-subproject-edit";
      editBtn.innerHTML =
        '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 17.25V21h3.75L17.8 9.94l-3.75-3.75L3 17.25zm17.71-10.04a1.003 1.003 0 0 0 0-1.42l-2.5-2.5a1.003 1.003 0 0 0-1.42 0l-1.96 1.96 3.75 3.75 2.13-2.79z"/></svg>';
      editBtn.title = `Modifier le sous-type « ${sub} »`;
      editBtn.setAttribute("aria-label", `Modifier le sous-type ${sub}`);
      editBtn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        openSubProjectAssignPanel(project);
      });
      subContent.appendChild(editBtn);
    }
    subTd.appendChild(subContent);
    tr.appendChild(subTd);

    const docTypesTd = document.createElement("td");
    docTypesTd.className = "doc-types-list";
    const docTypes = Array.isArray(project.doc_types) ? project.doc_types : [];
    docTypesTd.textContent = docTypes.length ? docTypes.join(", ") : "-";
    tr.appendChild(docTypesTd);

    tbody.appendChild(tr);
  });
  normalizeUiTree(tbody);
}


export async function loadProjectKinds() {
  try {
    const r = await api("/api/project-kinds");
    state.projectKinds = Array.isArray(r.projects) ? r.projects : [];
    renderProjectKinds(state.projectKinds, r.summary || {});
    settingsUiState.projectKindsLoadedOnce = true;
  } catch (e) {
    settingsUiState.projectKindsLoadedOnce = false;
    setStatus(e.message, true);
  }
}

// ----- DocType tags (CI import preview) -----

/**
 * Internal store for the current editor's DocTypes.
 * Each entry: { name: string, cat: "data" | "non_data" }
 */

export async function handleCiFileImport(file) {
  if (!file) return;
  _setCiImportStatus("Lecture en cours\u2026", false);
  const fd = new FormData();
  fd.append("file", file);
  try {
    const r = await fetch("/api/parse-ci-xlsx", {
      method: "POST",
      credentials: "same-origin",
      body: fd,
    });
    const json = await r.json();
    if (!r.ok || !json.ok) {
      _setCiImportStatus(json.error || "Erreur de lecture", true);
      return;
    }
    const data = (json.data || []).map((n) => ({ name: n, cat: "data" }));
    const nonData = (json.non_data || []).map((n) => ({ name: n, cat: "non_data" }));
    const all = [...data, ...nonData];
    _setEditorDocTypes(all);
    _setEditorFields(json.fields);
    _setEditorDoctypeFields(json.doctype_fields);
    _setEditorKeyingInstructions(json.keying_instructions);
    const dtMsg = `${all.length} DocType${all.length !== 1 ? "s" : ""} import\u00e9${all.length !== 1 ? "s" : ""} (${data.length} Data, ${nonData.length} Non-Data)`;
    const dtSheetCount = Object.keys(_editorDoctypeFields).length;
    const keyingCount = _editorKeyingInstructions.length;
    const extraParts = [];
    if (_editorFields.length) {
      extraParts.push(`${_editorFields.length} champ${_editorFields.length !== 1 ? "s" : ""}`);
    }
    if (dtSheetCount) {
      extraParts.push(`${dtSheetCount} feuille${dtSheetCount !== 1 ? "s" : ""} DocType`);
    }
    if (keyingCount) {
      extraParts.push(`${keyingCount} instruction${keyingCount !== 1 ? "s" : ""}`);
    }
    const fldMsg = extraParts.length ? ` \u2014 ${extraParts.join(", ")}` : "";
    _setCiImportStatus(dtMsg + fldMsg, false);
  } catch (e) {
    _setCiImportStatus(String(e && e.message ? e.message : e), true);
  }
}

// ----- Sub-project editor -----


export function closeSubProjectEditor() {
  subProjectEditorState.editingId = null;
  const wrap = $("subProjectEditorWrap");
  if (wrap) wrap.hidden = true;
  const nameEl = $("subProjectEditorName");
  if (nameEl) nameEl.value = "";
  const languageEl = $("subProjectEditorLanguage");
  if (languageEl) languageEl.value = "";
  document.querySelectorAll('input[name="subProjectRootKind"]').forEach((r) => {
    if (r instanceof HTMLInputElement) r.checked = r.value === "BDM";
  });
  _setEditorDocTypes([]);
  _setEditorFields([]);
  _setEditorDoctypeFields({});
  _setEditorKeyingInstructions([]);
  _setCiImportStatus("", false);
  const fileInput = $("subProjectCiFile");
  if (fileInput instanceof HTMLInputElement) fileInput.value = "";
}


export function openSubProjectEditor(existing) {
  const wrap = $("subProjectEditorWrap");
  const title = $("subProjectEditorTitle");
  if (!wrap) return;
  if (existing && existing.id != null) {
    subProjectEditorState.editingId = Number(existing.id);
    if (title) title.textContent = "Modifier le sous-type";
    const nameEl = $("subProjectEditorName");
    if (nameEl) nameEl.value = String(existing.name || "");
    const languageEl = $("subProjectEditorLanguage");
    if (languageEl) languageEl.value = String(existing.language || "");
    const rk = normalizeFieldsKindTab(existing.root_kind);
    document.querySelectorAll('input[name="subProjectRootKind"]').forEach((r) => {
      if (r instanceof HTMLInputElement) r.checked = r.value === rk;
    });
    const dts = Array.isArray(existing.doc_types) ? existing.doc_types : [];
    _setEditorFields(existing.fields);
    _setEditorDoctypeFields(existing.doctype_fields);
    _setEditorKeyingInstructions(existing.keying_instructions);
    const g = _editorDoctypeFields.__doc_type_groups__;
    let docTypeEntries;
    if (g && typeof g === "object") {
      const norm = (s) => String(s || "").trim().toLowerCase();
      const dataS = new Set((g.data || []).map(norm));
      const nonS = new Set((g.non_data || []).map(norm));
      docTypeEntries = dts.map((raw) => {
        const name = String(raw || "").trim();
        const nl = norm(name);
        let cat = "non_data";
        if (dataS.has(nl)) cat = "data";
        else if (nonS.has(nl)) cat = "non_data";
        return { name, cat };
      });
    } else {
      docTypeEntries = dts.map((n) => ({ name: n, cat: "non_data" }));
    }
    _setEditorDocTypes(docTypeEntries);
    const dtSheetCount = Object.keys(_editorDoctypeFields).filter((k) => !k.startsWith("__")).length;
    const keyingCount = _editorKeyingInstructions.length;
    const extraParts = [];
    if (_editorFields.length) {
      extraParts.push(`${_editorFields.length} champ${_editorFields.length !== 1 ? "s" : ""}`);
    }
    if (dtSheetCount) {
      extraParts.push(`${dtSheetCount} feuille${dtSheetCount !== 1 ? "s" : ""} DocType`);
    }
    if (keyingCount) {
      extraParts.push(`${keyingCount} instruction${keyingCount !== 1 ? "s" : ""}`);
    }
    const fldSuffix = extraParts.length ? ` \u2014 ${extraParts.join(", ")}` : "";
    _setCiImportStatus(dts.length ? `${dts.length} DocType${dts.length !== 1 ? "s" : ""} charg\u00e9${dts.length !== 1 ? "s" : ""}${fldSuffix}` : "", false);
  } else {
    subProjectEditorState.editingId = null;
    if (title) title.textContent = "Nouveau sous-type";
    const nameEl = $("subProjectEditorName");
    if (nameEl) nameEl.value = "";
    const languageEl = $("subProjectEditorLanguage");
    if (languageEl) languageEl.value = "";
    document.querySelectorAll('input[name="subProjectRootKind"]').forEach((r) => {
      if (r instanceof HTMLInputElement) r.checked = r.value === "BDM";
    });
    _setEditorDocTypes([]);
    _setEditorFields([]);
    _setEditorDoctypeFields({});
    _setEditorKeyingInstructions([]);
    _setCiImportStatus("", false);
  }
  const fileInput = $("subProjectCiFile");
  if (fileInput instanceof HTMLInputElement) fileInput.value = "";
  wrap.hidden = false;
  $("subProjectEditorName")?.focus?.();
}


export async function commitSubProject() {
  const nameEl = $("subProjectEditorName");
  const name = nameEl ? nameEl.value.trim() : "";
  const languageEl = $("subProjectEditorLanguage");
  const language = languageEl ? languageEl.value.trim() : "";
  const docTypes = _editorDocTypes.map((d) => d.name);
  const rootRadio = document.querySelector('input[name="subProjectRootKind"]:checked');
  const rootKind =
    rootRadio && rootRadio instanceof HTMLInputElement ? rootRadio.value : "BDM";
  const doctype_fields = Object.assign({}, _editorDoctypeFields);
  const gPayload = { data: [], non_data: [] };
  _editorDocTypes.forEach((d) => {
    const dn = String(d.name || "").trim();
    if (!dn) return;
    if (d.cat === "data") gPayload.data.push(dn);
    else gPayload.non_data.push(dn);
  });
  doctype_fields.__doc_type_groups__ = gPayload;
  const body = {
    name,
    root_kind: rootKind,
    language,
    doc_types: docTypes,
    fields: _editorFields,
    doctype_fields,
    keying_instructions: _editorKeyingInstructions,
  };
  try {
    if (subProjectEditorState.editingId != null) {
      await api(`/api/sub-projects/${subProjectEditorState.editingId}`, {
        method: "PUT",
        body: JSON.stringify(body),
      });
    } else {
      await api("/api/sub-projects", {
        method: "POST",
        body: JSON.stringify(body),
      });
    }
    closeSubProjectEditor();
    await loadSubProjects();
    await loadProjectKinds();
    setStatus("Sous-type enregistré.");
  } catch (e) {
    setStatus(String(e && e.message ? e.message : e), true);
  }
}


export async function deleteSubProjectById(subId) {
  if (
    typeof window.confirm === "function" &&
    !window.confirm("Supprimer ce sous-type ?")
  ) {
    return;
  }
  try {
    await api(`/api/sub-projects/${Number(subId)}`, { method: "DELETE" });
    await loadSubProjects();
    await loadProjectKinds();
    setStatus("Sous-type supprimé.");
  } catch (e) {
    setStatus(String(e && e.message ? e.message : e), true);
  }
}


export async function loadSubProjects() {
  try {
    const r = await api("/api/sub-projects");
    state.subProjects = Array.isArray(r.sub_projects) ? r.sub_projects : [];
    renderSubProjects(state.subProjects);
    renderFieldsKindTabs();
  } catch (e) {
    setStatus(String(e && e.message ? e.message : e), true);
  }
}


export function renderSubProjects(rows) {
  const tbody = $("subProjectsTableBody");
  if (!tbody) return;
  tbody.innerHTML = "";
  if (!rows || !rows.length) {
    const tr = document.createElement("tr");
    const td = document.createElement("td");
    td.colSpan = 4;
    td.className = "muted";
    td.textContent = "Aucun sous-type défini. Utilisez \u00ab\u00a0Ajouter\u00a0\u00bb pour en créer un.";
    tr.appendChild(td);
    tbody.appendChild(tr);
    normalizeUiTree(tbody);
    return;
  }
  rows.forEach((row) => {
    const tr = document.createElement("tr");
    tr.className = "sub-project-row";
    tr.setAttribute("tabindex", "0");
    tr.setAttribute("role", "button");
    tr.setAttribute("aria-label", `Voir le profil de ${row.name || ""}`);
    tr.addEventListener("click", () => openProfilePanel(row));
    tr.addEventListener("keydown", (e) => {
      if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openProfilePanel(row); }
    });

    const nameTd = document.createElement("td");
    nameTd.textContent = String(row.name || "");
    tr.appendChild(nameTd);

    const languageTd = document.createElement("td");
    languageTd.textContent = String(row.language || "").trim() || "\u2014";
    tr.appendChild(languageTd);

    const kindTd = document.createElement("td");
    const badge = document.createElement("span");
    const isCensus = String(row.root_kind || "").toLowerCase() === "census";
    badge.className = "kind-badge " + (isCensus ? "kind-badge--census" : "kind-badge--bdm");
    badge.textContent = isCensus ? "Census" : "BDM";
    kindTd.appendChild(badge);
    tr.appendChild(kindTd);

    const dtTd = document.createElement("td");
    dtTd.className = "doc-types-list";
    const dts = Array.isArray(row.doc_types) ? row.doc_types : [];
    dtTd.textContent = dts.length ? dts.join(", ") : "\u2014";
    tr.appendChild(dtTd);

    tbody.appendChild(tr);
  });
  normalizeUiTree(tbody);
}


export function initSubProjectsSettings() {
  $("btnSubProjectCancel")?.addEventListener("click", () => closeSubProjectEditor());
  $("btnSubProjectSave")?.addEventListener("click", () => void commitSubProject());

  const btnImport = $("btnCiImport");
  const fileInput = $("subProjectCiFile");
  if (btnImport && fileInput) {
    btnImport.addEventListener("click", () => fileInput.click());
    fileInput.addEventListener("change", () => {
      const f = fileInput instanceof HTMLInputElement ? fileInput.files?.[0] : null;
      if (f) void handleCiFileImport(f);
    });
  }
}


// ----- Sub-project profile panel -----

/**
 * Build a flat, deduplicated field list from fieldsByKindConfig for the
 * given sub-project name (e.g. "Census" or "BDM").
 * Returns [] when the name has no matching hardcoded config.
 */

export async function openProfilePanel(subProject) {
  let sp = subProject;

  // Lazy-seed: if this sub-project has no fields yet AND its name matches a
  // hardcoded config key (e.g. "Census" or "BDM"), build the default field
  // list, persist it to the DB, and update the local state in one pass.
  if ((!sp.fields || !sp.fields.length) && sp.id != null) {
    const defaults = _buildDefaultFieldsFromConfig(sp.name);
    if (defaults.length) {
      try {
        const r = await api(`/api/sub-projects/${sp.id}`, {
          method: "PUT",
          body: JSON.stringify({ fields: defaults }),
        });
        if (r && r.ok && r.sub_project) {
          sp = r.sub_project;
          state.subProjects = (state.subProjects || []).map((p) =>
            Number(p.id) === Number(sp.id) ? sp : p
          );
        }
      } catch (_) {
        // Non-fatal: display with in-memory defaults even if save failed.
        sp = Object.assign({}, sp, { fields: defaults });
      }
    }
  }

  subProfilePanelState.subProject = sp;
  subProfilePanelState.thresholdEditMode = false;
  subProfilePanelState.fieldSearchQuery = "";
  subProfilePanelState.editSnapshot = null;
  subProfilePanelState.pendingDocTypeAddFocus = null;
  const sInpReset = $("subProfileFieldSearch");
  if (sInpReset instanceof HTMLInputElement) sInpReset.value = "";
  _clearProfileCiImportUi();
  _profilePanelUpdateEditChrome();
  _renderProfilePanel(sp);
  const backdrop = $("subProfileBackdrop");
  const panel = $("subProfilePanel");
  if (backdrop) backdrop.hidden = false;
  if (panel) { panel.hidden = false; panel.focus(); }
}


export function closeProfilePanel() {
  subProfilePanelState.subProject = null;
  subProfilePanelState.thresholdEditMode = false;
  subProfilePanelState.fieldSearchQuery = "";
  subProfilePanelState.editSnapshot = null;
  subProfilePanelState.pendingDocTypeAddFocus = null;
  const sInp = $("subProfileFieldSearch");
  if (sInp instanceof HTMLInputElement) sInp.value = "";
  _clearProfileCiImportUi();
  _profilePanelUpdateEditChrome();
  const backdrop = $("subProfileBackdrop");
  const panel = $("subProfilePanel");
  if (backdrop) backdrop.hidden = true;
  if (panel) panel.hidden = true;
}


/**
 * Parse a CI Excel file and apply only Keying Instructions to the profile panel
 * sub-project (in-memory until Enregistrer).
 */
export async function handleProfilePanelCiFileImport(file) {
  if (!file) return;
  const sp = subProfilePanelState.subProject;
  if (!sp || !subProfilePanelState.thresholdEditMode) return;
  _setProfileCiImportStatus("Lecture en cours\u2026", false);
  const fd = new FormData();
  fd.append("file", file);
  try {
    const r = await fetch("/api/parse-ci-xlsx", {
      method: "POST",
      credentials: "same-origin",
      body: fd,
    });
    const json = await r.json();
    if (!r.ok || !json.ok) {
      _setProfileCiImportStatus(json.error || "Erreur de lecture", true);
      return;
    }
    const instructions = Array.isArray(json.keying_instructions) ? json.keying_instructions : [];
    sp.keying_instructions = instructions;
    _renderProfilePanel(sp);
    const count = instructions.length;
    if (!count) {
      _setProfileCiImportStatus("Aucune Keying Instruction trouvée dans ce fichier CI.", true);
      return;
    }
    _setProfileCiImportStatus(
      `${count} instruction${count !== 1 ? "s" : ""} import\u00e9e${count !== 1 ? "s" : ""} \u2014 cliquez Enregistrer pour conserver.`,
      false
    );
  } catch (e) {
    _setProfileCiImportStatus(String(e && e.message ? e.message : e), true);
  }
}


export async function saveProfilePanelThresholds() {
  const sp = subProfilePanelState.subProject;
  if (!sp || sp.id == null || !subProfilePanelState.thresholdEditMode) return;
  const nameInput = $("subProfileTitleInput");
  const newName =
    nameInput instanceof HTMLInputElement
      ? String(nameInput.value || "").trim()
      : String(sp.name || "").trim();
  const languageInput = $("subProfileLanguageInput");
  const newLanguage =
    languageInput instanceof HTMLInputElement
      ? String(languageInput.value || "").trim()
      : String(sp.language || "").trim();
  if (!newName) {
    setStatus("Le nom du sous-projet est requis.", true);
    nameInput?.focus?.();
    return;
  }
  const fieldsIn = Array.isArray(sp.fields) ? sp.fields : [];
  const fields = JSON.parse(JSON.stringify(fieldsIn));
  const byName = /** @type {Record<string, object>} */ (
    Object.fromEntries(fields.map((f) => [String(f.field_name || ""), f]))
  );
  document.querySelectorAll("#subProfileFieldsBody .sub-profile-fields-threshold-input").forEach((inp) => {
    if (!(inp instanceof HTMLInputElement)) return;
    const name = String(inp.dataset.fieldName || "").trim();
    if (!name || !byName[name]) return;
    const raw = String(inp.value).trim();
    if (raw === "") {
      byName[name].rekey_threshold = null;
    } else {
      const n = parseInt(raw, 10);
      if (isFinite(n)) {
        byName[name].rekey_threshold = Math.max(0, Math.min(100, n));
      }
    }
  });
  const body = {
    name: newName,
    root_kind: sp.root_kind,
    language: newLanguage,
    doc_types: Array.isArray(sp.doc_types) ? sp.doc_types : [],
    fields,
    doctype_fields:
      sp.doctype_fields && typeof sp.doctype_fields === "object" ? sp.doctype_fields : {},
    keying_instructions: Array.isArray(sp.keying_instructions) ? sp.keying_instructions : [],
  };
  try {
    const r = await api(`/api/sub-projects/${Number(sp.id)}`, {
      method: "PUT",
      body: JSON.stringify(body),
    });
    const updated = r && r.ok && r.sub_project ? r.sub_project : null;
    const nextSp = updated || Object.assign({}, sp, { fields });
    subProfilePanelState.subProject = nextSp;
    state.subProjects = (state.subProjects || []).map((p) =>
      Number(p.id) === Number(nextSp.id) ? nextSp : p
    );
    renderSubProjects(state.subProjects);
    renderFieldsKindTabs();
    subProfilePanelState.thresholdEditMode = false;
    subProfilePanelState.fieldSearchQuery = "";
    subProfilePanelState.editSnapshot = null;
    const sInp = $("subProfileFieldSearch");
    if (sInp instanceof HTMLInputElement) sInp.value = "";
    _clearProfileCiImportUi();
    _profilePanelUpdateEditChrome();
    _renderProfilePanel(subProfilePanelState.subProject);
    setStatus("Modifications enregistr\u00e9es.");
  } catch (e) {
    setStatus(String(e && e.message ? e.message : e), true);
  }
}


export function initProfilePanel() {
  $("btnProfileClose")?.addEventListener("click", () => closeProfilePanel());
  $("subProfileBackdrop")?.addEventListener("click", () => closeProfilePanel());

  $("btnProfileEdit")?.addEventListener("click", () => {
    const sp = subProfilePanelState.subProject;
    if (!sp) return;
    if (subProfilePanelState.thresholdEditMode) {
      cancelProfilePanelThresholdEdit();
      return;
    }
    subProfilePanelState.thresholdEditMode = true;
    subProfilePanelState.fieldSearchQuery = "";
    subProfilePanelState.editSnapshot = {
      name: sp.name,
      language: sp.language,
      fields: JSON.parse(JSON.stringify(Array.isArray(sp.fields) ? sp.fields : [])),
      keying_instructions: JSON.parse(
        JSON.stringify(Array.isArray(sp.keying_instructions) ? sp.keying_instructions : [])
      ),
      doc_types: JSON.parse(JSON.stringify(Array.isArray(sp.doc_types) ? sp.doc_types : [])),
      doctype_fields: JSON.parse(
        JSON.stringify(
          sp.doctype_fields && typeof sp.doctype_fields === "object" ? sp.doctype_fields : {}
        )
      ),
    };
    _clearProfileCiImportUi();
    const sInp = $("subProfileFieldSearch");
    if (sInp instanceof HTMLInputElement) sInp.value = "";
    _profilePanelUpdateEditChrome();
    _renderProfilePanel(sp);
    requestAnimationFrame(() => $("subProfileTitleInput")?.focus?.());
  });

  const btnProfileCiImport = $("btnProfileCiImport");
  const profileCiFile = $("subProfileCiFile");
  if (btnProfileCiImport && profileCiFile) {
    btnProfileCiImport.addEventListener("click", () => profileCiFile.click());
    profileCiFile.addEventListener("change", () => {
      const f = profileCiFile instanceof HTMLInputElement ? profileCiFile.files?.[0] : null;
      if (f) void handleProfilePanelCiFileImport(f);
    });
  }

  $("btnProfileThresholdSave")?.addEventListener("click", () => {
    void saveProfilePanelThresholds();
  });

  $("subProfileFieldSearch")?.addEventListener("input", (ev) => {
    if (!(ev.target instanceof HTMLInputElement)) return;
    subProfilePanelState.fieldSearchQuery = ev.target.value;
    _applySubProfileFieldSearchFilter();
  });

  $("btnProfileDelete")?.addEventListener("click", () => {
    const sp = subProfilePanelState.subProject;
    if (!sp) return;
    closeProfilePanel();
    void deleteSubProjectById(sp.id);
  });

  document.addEventListener("keydown", (e) => {
    if (e.key !== "Escape" || !subProfilePanelState.subProject) return;
    if (subProfilePanelState.thresholdEditMode) {
      e.preventDefault();
      cancelProfilePanelThresholdEdit();
      return;
    }
    closeProfilePanel();
  });
}
