/* Immobase App — store (Supabase + profil situationnel) */

/* localStorage namespacé par utilisateur (P0-09) : chaque compte a sa propre
 * clé, jamais partagée. La clé « anon » ne sert qu'avant résolution de session
 * et n'est jamais synchronisée en base. */
const IMMO_PREFIX = 'immobase_state_v2_';
const IMMO_LEGACY_KEYS = ['immobase_state_v2', 'immobase_geocode_v1'];
const stateKeyFor = (uid) => IMMO_PREFIX + (uid || 'anon');

/* Purge TOTALE des données locales Immoly (appelée au signOut, P0-09). */
function purgeImmobaseLocal() {
  try {
    const toRemove = [];
    for (let i = 0; i < localStorage.length; i++) {
      const k = localStorage.key(i);
      if (k && (k.startsWith('immobase_') || k.startsWith(IMMO_PREFIX))) toRemove.push(k);
    }
    toRemove.forEach((k) => localStorage.removeItem(k));
  } catch (e) {}
}

/* Statuts du pipeline d'achat (s4q02). « Coup de cœur » n'est PLUS un statut :
 * c'est un tag distinct « favori » (s10q10). */
const STATUS_LABELS = {
  'a-visiter':            'À visiter',
  'visite':               'Visité',
  'offre-envoyee':        'Offre envoyée',
  'compromis-signe':      'Compromis signé',
  'financement-en-cours': 'Financement en cours',
  'signe-acte':           'Signé / acte authentique',
  'elimine':              'Éliminé',
};
const STATUS_ORDER = ['a-visiter', 'visite', 'offre-envoyee', 'compromis-signe', 'financement-en-cours', 'signe-acte', 'elimine'];
/* Normalise un statut hérité (ancien « coup-de-coeur ») vers le nouveau modèle. */
const normalizeStatus = (s) => (s === 'coup-de-coeur' ? 'a-visiter' : (STATUS_LABELS[s] ? s : 'a-visiter'));

/* ── Liste complète des documents avec conditions ─────────── */
/* name = intitulé du document (gras) · note = précision (sous-titre) */
const ALL_DOCS = [
  // ── Notaire ──────────────────────────────────────────────
  { id: 'piece_identite',     recipient: 'notaire',     name: "Pièce d'identité",                   note: "carte d'identité ou passeport en cours de validité", conditions: [] },
  { id: 'compromis',          recipient: 'notaire',     name: 'Compromis de vente',                 note: 'avant-contrat signé entre acheteur et vendeur',      conditions: [] },
  { id: 'titre_propriete',    recipient: 'notaire',     name: 'Titre de propriété du vendeur',      note: "acte de propriété actuel du bien",                   conditions: [] },
  { id: 'etat_hypothecaire',  recipient: 'notaire',     name: 'État hypothécaire',                  note: "vérifie l'absence de dettes grevant le bien",        conditions: [] },
  { id: 'acte_authentique',   recipient: 'notaire',     name: 'Acte authentique de vente',          note: 'signature définitive chez le notaire',               conditions: [] },
  { id: 'reglement_copro',    recipient: 'notaire',     name: 'Règlement de copropriété',           note: 'règles de vie et répartition des charges',           conditions: ['appartement'] },
  { id: 'pv_ag',              recipient: 'notaire',     name: 'PV des 3 dernières AG',              note: 'décisions et travaux votés en copropriété',          conditions: ['appartement'] },
  { id: 'pre_etat_date',      recipient: 'notaire',     name: 'Pré-état daté',                       note: 'situation financière du lot en copropriété',         conditions: ['appartement'] },
  { id: 'carnet_entretien',   recipient: 'notaire',     name: "Carnet d'entretien de l'immeuble",   note: 'historique des travaux de la copropriété',           conditions: ['appartement'] },

  // ── Diagnostics (DDT — fournis par le vendeur) ───────────
  { id: 'dpe',                recipient: 'diagnostics', name: 'DPE',                                note: 'diagnostic de performance énergétique',              conditions: [] },
  { id: 'erp',                recipient: 'diagnostics', name: 'État des risques (ERP)',             note: 'risques naturels, miniers et technologiques',        conditions: [] },
  { id: 'amiante',            recipient: 'diagnostics', name: 'Diagnostic amiante',                 note: 'pour les biens construits avant 1997',               conditions: [] },
  { id: 'plomb',              recipient: 'diagnostics', name: 'CREP (plomb)',                       note: 'pour les biens construits avant 1949',               conditions: [] },
  { id: 'electricite',        recipient: 'diagnostics', name: 'Diagnostic électricité',            note: 'installation de plus de 15 ans',                     conditions: [] },
  { id: 'gaz',                recipient: 'diagnostics', name: 'Diagnostic gaz',                     note: 'installation de plus de 15 ans',                     conditions: [] },
  { id: 'termites',           recipient: 'diagnostics', name: 'Diagnostic termites',               note: 'si le bien est situé en zone concernée',             conditions: [] },
  { id: 'carrez',             recipient: 'diagnostics', name: 'Mesurage Loi Carrez',               note: 'surface privative exacte du lot',                    conditions: ['appartement'] },
  { id: 'assainissement',     recipient: 'diagnostics', name: 'Diagnostic assainissement',         note: 'maison non raccordée au tout-à-l\'égout',            conditions: ['maison'] },

  // ── Banque (financement) ─────────────────────────────────
  { id: 'avis_imposition',    recipient: 'banque',      name: "Avis d'imposition",                  note: 'vos deux derniers avis',                             conditions: ['credit'] },
  { id: 'releves_bancaires',  recipient: 'banque',      name: 'Relevés bancaires (3 mois)',         note: 'vos trois derniers relevés',                         conditions: ['credit'] },
  { id: 'justif_apport',      recipient: 'banque',      name: "Justificatif d'apport",              note: 'épargne, donation, prêt familial…',                  conditions: ['credit'] },
  { id: 'bulletins_salaire',  recipient: 'banque',      name: 'Bulletins de salaire (3 mois)',      note: 'vos trois derniers bulletins',                       conditions: ['credit', 'salarie'] },
  { id: 'contrat_travail',    recipient: 'banque',      name: 'Contrat de travail',                 note: 'ou attestation employeur',                           conditions: ['credit', 'salarie'] },
  { id: 'bilans',             recipient: 'banque',      name: 'Bilans comptables (2-3 ans)',        note: 'pour les indépendants et dirigeants',                conditions: ['credit', 'independant'] },
  { id: 'kbis',               recipient: 'banque',      name: 'Extrait Kbis',                       note: "justificatif d'immatriculation de l'entreprise",     conditions: ['credit', 'independant'] },
  { id: 'justif_pension',     recipient: 'banque',      name: 'Justificatifs de pension',           note: 'relevés de retraite',                                conditions: ['credit', 'retraite'] },
  { id: 'offre_pret',         recipient: 'banque',      name: 'Offre de prêt',                      note: 'émise par la banque après accord',                   conditions: ['credit'] },
  { id: 'tableau_amort',      recipient: 'banque',      name: "Tableau d'amortissement",            note: 'échéancier détaillé du crédit',                      conditions: ['credit'] },

  // ── Assurance ────────────────────────────────────────────
  { id: 'assurance_hab',      recipient: 'assurance',   name: "Attestation d'assurance habitation", note: 'obligatoire le jour de la signature',                conditions: [] },
  { id: 'assurance_empr',     recipient: 'assurance',   name: 'Assurance emprunteur',               note: 'couvre le remboursement du prêt',                    conditions: ['credit'] },
];

/* Filtre les docs selon le profil de l'utilisateur */
function matchesProfile(doc, profile) {
  if (!doc.conditions || doc.conditions.length === 0) return true;
  if (!profile || (!profile.type_bien && !profile.financement && !profile.statut_emploi)) return true; // Pas encore de profil = tout afficher
  return doc.conditions.every(cond => {
    switch (cond) {
      case 'appartement':   return profile.type_bien === 'appartement';
      case 'maison':        return profile.type_bien === 'maison';
      case 'credit':        return profile.financement === 'credit';
      case 'cash':          return profile.financement === 'cash';
      case 'salarie':       return profile.statut_emploi === 'salarie';
      case 'independant':   return profile.statut_emploi === 'independant';
      case 'retraite':      return profile.statut_emploi === 'retraite';
      default:              return true;
    }
  });
}

function computeDocuments(profile, checkedDocs, customDocs) {
  const base = ALL_DOCS
    .filter(doc => matchesProfile(doc, profile))
    .map(doc => ({ ...doc, done: checkedDocs?.[doc.id] || false }));
  // Lignes personnalisées ajoutées par l'utilisateur (s10q08)
  const custom = (customDocs || []).map((d) => ({
    id: d.id, recipient: 'perso', name: d.name, note: 'ajouté par vous',
    conditions: [], custom: true, done: checkedDocs?.[d.id] || false,
  }));
  return base.concat(custom);
}

const EMPTY_PROFILE = {
  type_bien:     null,
  financement:   null,
  statut_emploi: null,
};

function emptyState() {
  const profile = { ...EMPTY_PROFILE };
  return {
    properties: [],
    checkedDocs: {},
    profile,
    documents: computeDocuments(profile, {}, []),
    sim: { price: 300000, apport: 60000, duree: 20, taux: 3.4 },
    visiteCriteres: [],
    customDocs: [],
  };
}

/* Bien d'exemple réaliste (s9q10) : clairement marqué, jamais synchronisé en
 * base, supprimable en un clic. */
function demoProperty() {
  return {
    id: 'demo-exemple',
    demo: true,
    title: 'Exemple — Maison lumineuse',
    type: 'Maison',
    city: 'Nantes, Loire-Atlantique',
    price: 298000,
    surface: 110,
    rooms: 5,
    floor: 0,
    status: 'a-visiter',
    favori: false,
    visitDate: '',
    agentName: 'Camille Durand',
    agentAgency: 'Agence du Port',
    agentPhone: '',
    notes: "Bien d'exemple pour découvrir Immoly : ouvrez la fiche, changez le statut, testez la grille de visite… puis supprimez-le en un clic.",
    url: '',
    photoUrl: '',
    fees: 8900,
    charges: null,
    dpe: 'C',
    travaux: 15000,
    description: '',
    visiteGrille: null,
  };
}

/* ── Conversion biens ────────────────────────────────────── */
function dbToProperty(row) {
  return {
    id:          row.id,
    title:       row.titre || '',
    type:        row.type_bien || 'Appartement',
    city:        row.localisation || '',
    price:       row.prix || 0,
    surface:     row.surface || 0,
    rooms:       row.pieces || 0,
    floor:       row.etage || 0,
    status:      normalizeStatus(row.statut || 'a-visiter'),
    favori:      (row.favori ?? (row.statut === 'coup-de-coeur')) || false,
    visitDate:   row.date_visite || '',
    agentName:   row.nom_agent || '',
    agentAgency: row.nom_agence || '',
    agentPhone:  row.telephone_agent || '',
    notes:       row.notes || '',
    url:         row.url_source || '',
    // photo_principale : soit une URL http (image externe), soit un chemin
    // « userId/uuid.ext » dans le bucket privé (résolu en URL signée, P0-08)
    photoPath:   (row.photo_principale && !/^https?:/i.test(row.photo_principale)) ? row.photo_principale : '',
    photoUrl:    (row.photo_principale && /^https?:/i.test(row.photo_principale)) ? row.photo_principale : '',
    fees:        row.frais_agence != null ? row.frais_agence : null,
    charges:     row.charges_mensuelles != null ? row.charges_mensuelles : null,
    dpe:         row.dpe || '',
    travaux:     row.budget_travaux != null ? row.budget_travaux : null,
    description: row.description || '',
    visiteGrille: row.visite_grille || null,
  };
}

/* Convertit une valeur « frais/charges/travaux » saisie en entier ou null.
 * Chaîne vide / non renseigné → null (affiché « — », exclu du verdict, s10q01). */
function numOrNull(v) {
  if (v === '' || v === null || v === undefined) return null;
  const n = Number(v);
  return Number.isFinite(n) ? n : null;
}

function propertyToDb(p, userId) {
  return {
    user_id:            userId,
    url_source:         p.url || null,
    titre:              p.title,
    type_bien:          p.type || 'Appartement',
    prix:               Number(p.price) || 0,
    surface:            Number(p.surface) || 0,
    pieces:             Number(p.rooms) || 0,
    etage:              Number(p.floor) || 0,
    localisation:       p.city || null,
    pays:               'FR',
    description:        p.description || null,
    photo_principale:   p.photoPath || p.photoUrl || null,
    statut:             p.status || 'a-visiter',
    date_visite:        p.visitDate || null,
    nom_agent:          p.agentName || null,
    nom_agence:         p.agentAgency || null,
    telephone_agent:    p.agentPhone || null,
    notes:              p.notes || null,
    frais_agence:       numOrNull(p.fees),
    charges_mensuelles: numOrNull(p.charges),
    dpe:                p.dpe || null,
    budget_travaux:     numOrNull(p.travaux),
    favori:             !!p.favori,
    visite_grille:      p.visiteGrille || null,
  };
}

const RECIPIENTS = {
  notaire:     { label: 'Notaire',     color: 'sage' },
  diagnostics: { label: 'Diagnostics', color: 'blue' },
  banque:      { label: 'Banque',      color: 'clay' },
  assurance:   { label: 'Assurance',   color: 'sage' },
  perso:       { label: 'Vos ajouts',  color: 'clay' },
};

const EMAIL_TEMPLATES = {
  compromis: { to: 'contact@agence.fr', subject: 'Compromis de vente — {{bien}}',
    body: "Bonjour,\n\nSuite à notre visite du bien situé {{bien}}, je souhaite avancer vers la signature du compromis de vente.\n\nPourriez-vous me transmettre le projet de compromis et m'indiquer les prochaines étapes ainsi que le montant du dépôt de garantie ?\n\nJe reste à votre disposition.\nBien cordialement,\n{{nom}}" },

  reglement_copro: { to: 'syndic@cabinet.fr', subject: 'Demande de règlement de copropriété — {{bien}}',
    body: "Bonjour,\n\nDans le cadre de l'achat du bien situé {{bien}}, j'aurais besoin du règlement de copropriété à jour.\n\nPourriez-vous me le transmettre ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },

  pv_ag: { to: 'syndic@cabinet.fr', subject: "Demande des PV d'assemblée générale — {{bien}}",
    body: "Bonjour,\n\nPour finaliser mon projet d'achat ({{bien}}), je souhaiterais consulter les procès-verbaux des trois dernières assemblées générales de copropriété.\n\nPourriez-vous me les faire parvenir ?\n\nMerci d'avance.\nBien cordialement,\n{{nom}}" },

  pre_etat_date: { to: 'syndic@cabinet.fr', subject: 'Demande de pré-état daté — {{bien}}',
    body: "Bonjour,\n\nDans le cadre de la vente du bien situé {{bien}}, le notaire a besoin du pré-état daté.\n\nPourriez-vous l'établir et m'indiquer le délai et le coût ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },

  dpe: { to: 'contact@agence.fr', subject: 'Diagnostics techniques (DDT) — {{bien}}',
    body: "Bonjour,\n\nConcernant le bien situé {{bien}}, pourriez-vous me transmettre le dossier de diagnostics techniques (DPE, état des risques, et le cas échéant amiante, plomb, électricité, gaz…) ?\n\nJe vous en remercie.\nBien cordialement,\n{{nom}}" },

  avis_imposition: { to: 'conseiller@mabanque.fr', subject: 'Justificatifs pour mon dossier de prêt',
    body: "Bonjour,\n\nDans le cadre de mon projet d'achat immobilier ({{bien}}), je prépare mon dossier de financement.\n\nPourriez-vous m'indiquer la liste exacte des justificatifs à fournir pour étudier ma demande de prêt ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },

  releves_bancaires: { to: 'conseiller@mabanque.fr', subject: 'Relevés bancaires pour dossier immobilier',
    body: "Bonjour,\n\nPour mon dossier de financement ({{bien}}), j'aurais besoin de mes trois derniers relevés bancaires.\n\nPourriez-vous me les transmettre ou m'indiquer comment les obtenir au format requis ?\n\nMerci d'avance.\nBien cordialement,\n{{nom}}" },

  bulletins_salaire: { to: 'rh@employeur.fr', subject: 'Demande de bulletins de salaire',
    body: "Bonjour,\n\nDans le cadre d'un dossier de prêt immobilier ({{bien}}), j'aurais besoin de mes trois derniers bulletins de salaire.\n\nPourriez-vous me les transmettre ?\n\nMerci beaucoup.\nBien cordialement,\n{{nom}}" },

  offre_pret: { to: 'conseiller@mabanque.fr', subject: 'Suivi de mon offre de prêt — {{bien}}',
    body: "Bonjour,\n\nConcernant le financement de mon achat ({{bien}}), pourriez-vous me préciser où en est l'édition de l'offre de prêt et le délai de réception ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },

  assurance_hab: { to: 'contact@assureur.fr', subject: "Attestation d'assurance habitation — {{bien}}",
    body: "Bonjour,\n\nJe finalise l'achat du bien situé {{bien}} et le notaire me demande une attestation d'assurance habitation pour le jour de la signature.\n\nPourriez-vous me l'établir ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },

  assurance_empr: { to: 'contact@assureur.fr', subject: "Devis d'assurance emprunteur — {{bien}}",
    body: "Bonjour,\n\nDans le cadre de mon prêt immobilier ({{bien}}), je souhaiterais obtenir un devis d'assurance emprunteur.\n\nPourriez-vous m'indiquer les garanties proposées et le tarif ?\n\nJe vous remercie.\nBien cordialement,\n{{nom}}" },
};

/* ─── Hook principal ──────────────────────────────────────── */
function useStore() {
  const sb = window.sb;
  /* userId en state React pour réagir à la résolution async de l'auth */
  const [userId, setUserId] = React.useState(window.IMMOBASE_USER?.id || null);

  React.useEffect(() => {
    const { data: { subscription } } = sb.auth.onAuthStateChange((_event, session) => {
      const uid = session?.user?.id || null;
      if (session?.user) window.IMMOBASE_USER = session.user;
      setUserId(uid);
    });
    return () => subscription.unsubscribe();
  }, []);

  const [state, setState] = React.useState(emptyState);

  /* Nettoyage unique des anciennes clés globales non namespacées (pré-P0-09). */
  React.useEffect(() => {
    try { IMMO_LEGACY_KEYS.forEach((k) => localStorage.removeItem(k)); } catch (e) {}
  }, []);

  /* Chargement du cache local NAMESPACÉ quand l'utilisateur est résolu.
   * Aucune donnée n'est lue avant de connaître le userId → pas de fuite
   * entre comptes sur le même navigateur (P0-09). */
  React.useEffect(() => {
    if (!userId) return;
    try {
      const raw = localStorage.getItem(stateKeyFor(userId));
      if (raw) {
        const parsed = JSON.parse(raw);
        if (!parsed.properties?.some((p) => p.id === 'p1')) {
          setState((s) => ({ ...emptyState(), ...parsed }));
        }
      }
    } catch (e) {}
  }, [userId]);

  /* Cache localStorage — écrit UNIQUEMENT sous la clé de l'utilisateur courant. */
  React.useEffect(() => {
    if (!userId) return;
    try { localStorage.setItem(stateKeyFor(userId), JSON.stringify(state)); } catch (e) {}
  }, [state, userId]);

  /* Bucket privé (P0-08) : résout les chemins « userId/uuid.ext » en URLs
   * signées (24 h) pour l'affichage. Relancé à chaque chargement de biens. */
  async function resolveSignedPhotos(props) {
    const paths = (props || []).filter((p) => p.photoPath).map((p) => p.photoPath);
    if (!paths.length || !sb) return;
    try {
      const { data, error } = await sb.storage.from('property-photos').createSignedUrls(paths, 60 * 60 * 24);
      if (error || !data) return;
      const map = {};
      data.forEach((d) => { if (d.signedUrl && d.path) map[d.path] = d.signedUrl; });
      setState((s) => ({
        ...s,
        properties: s.properties.map((p) => (p.photoPath && map[p.photoPath]) ? { ...p, photoUrl: map[p.photoPath] } : p),
      }));
    } catch (e) { /* photo indisponible : la fiche reste utilisable */ }
  }

  /* Chargement initial depuis Supabase */
  React.useEffect(() => {
    if (!userId || !sb) return;

    Promise.all([
      sb.from('biens').select('*').eq('user_id', userId).order('created_at', { ascending: false }),
      sb.from('checklist_documents').select('local_id, est_coche').eq('user_id', userId),
      sb.from('user_settings').select('*').eq('user_id', userId).maybeSingle(),
    ]).then(([biensRes, docsRes, simRes]) => {
      const updates = {};

      if (biensRes.error) console.error('[immobase] lecture biens', biensRes.error);
      if (docsRes.error)  console.error('[immobase] lecture documents', docsRes.error);
      if (simRes.error && simRes.error.code !== 'PGRST116') console.error('[immobase] lecture user_settings', simRes.error);

      const checkedDocs = {};
      if (!docsRes.error && docsRes.data) {
        docsRes.data.forEach(d => { if (d.local_id) checkedDocs[d.local_id] = d.est_coche; });
      }
      updates.checkedDocs = checkedDocs;

      const profile = { ...EMPTY_PROFILE };
      if (!simRes.error && simRes.data) {
        profile.type_bien      = simRes.data.type_bien      ?? null;
        profile.financement    = simRes.data.financement    ?? null;
        profile.statut_emploi  = simRes.data.statut_emploi ?? null;
        updates.visiteCriteres = simRes.data.visite_criteres_perso || [];
        updates.customDocs     = simRes.data.checklist_custom || [];
        updates.sim = {
          price:  simRes.data.sim_price  ?? 300000,
          apport: simRes.data.sim_apport ?? 60000,
          duree:  simRes.data.sim_duree  ?? 20,
          taux:   Number(simRes.data.sim_taux ?? 3.4),
        };
      }
      updates.profile   = profile;

      // Biens locaux non encore synchronisés (id temporaire 'p…') — à préserver / resync
      let toSync = [];
      setState(s => {
        const merged = { ...s, ...updates };
        if (!biensRes.error && Array.isArray(biensRes.data)) {
          const dbProps = biensRes.data.map(dbToProperty);
          const dbIds = new Set(dbProps.map(p => p.id));
          const localOnly = (s.properties || []).filter(
            p => typeof p.id === 'string' && (p.id.startsWith('p') || p.demo) && !dbIds.has(p.id)
          );
          toSync = localOnly.filter(p => !p.demo); // le bien d'exemple ne va JAMAIS en base
          merged.properties = [...localOnly, ...dbProps]; // ne JAMAIS écraser les biens locaux
        }
        merged.documents = computeDocuments(merged.profile, merged.checkedDocs, merged.customDocs);
        return merged;
      });

      // Photos du bucket privé → URLs signées pour l'affichage (P0-08)
      if (!biensRes.error && Array.isArray(biensRes.data)) {
        resolveSignedPhotos(biensRes.data.map(dbToProperty));
      }

      // Tente de pousser en base les biens restés en local (réussit une fois le schéma corrigé)
      if (userId && sb && toSync.length) {
        toSync.forEach(async (p) => {
          const { data, error } = await sb.from('biens').insert(propertyToDb(p, userId)).select().single();
          if (!error && data) {
            setState(s => ({ ...s, properties: s.properties.map(x => x.id === p.id ? dbToProperty(data) : x) }));
          }
        });
      }
    });
  }, [userId]);

  const api = React.useMemo(() => ({
    state,

    /* Ajoute le bien d'exemple (local uniquement, jamais en base — s9q10). */
    addDemoProperty() {
      if (state.properties.some((x) => x.demo)) { toast("Le bien d'exemple est déjà dans votre liste"); return; }
      setState(s => ({ ...s, properties: [demoProperty(), ...s.properties] }));
      toast("Bien d'exemple ajouté — supprimez-le quand vous voulez");
    },

    async addProperty(p) {
      // Le bien d'exemple ne compte pas dans la limite du plan gratuit.
      const countable = state.properties.filter((x) => !x.demo).length;
      if (!window.isPro() && countable >= window.FREE_PLAN_MAX_PROPERTIES) {
        if (window.openLimitWall) window.openLimitWall(); // mur des biens (s5q13)
        else toast(`Limite de ${window.FREE_PLAN_MAX_PROPERTIES} biens atteinte — passez au plan Complet`);
        return;
      }
      if (userId && sb) {
        const { data, error } = await sb.from('biens').insert(propertyToDb(p, userId)).select().single();
        if (!error && data) {
          window.track?.('property_added', { count_after: countable + 1 });
          const np = dbToProperty(data);
          // conserve l'aperçu signé déjà obtenu au moment de l'upload
          if (!np.photoUrl && p.photoUrl) np.photoUrl = p.photoUrl;
          setState(s => ({ ...s, properties: [np, ...s.properties] }));
          return;
        }
        if (error) {
          console.error('[immobase] insertion bien', error);
          toast('⚠ Sauvegarde en ligne impossible — bien gardé sur cet appareil seulement');
        }
      }
      setState(s => ({ ...s, properties: [{ ...p, id: 'p' + Date.now() }, ...s.properties] }));
    },

    async updateProperty(id, patch) {
      const current = state.properties.find(p => p.id === id);
      const merged = { ...current, ...patch };
      setState(s => ({ ...s, properties: s.properties.map(p => p.id === id ? merged : p) }));
      // id 'p…' = bien non synchronisé ; 'demo…' = bien d'exemple : jamais en base
      if (userId && sb && current && !String(id).startsWith('p') && !String(id).startsWith('demo')) {
        const { error } = await sb.from('biens').update(propertyToDb(merged, userId)).eq('id', id);
        if (error) console.error('[immobase] maj bien', error);
      }
    },

    async removeProperty(id) {
      const prop = state.properties.find((p) => p.id === id);
      setState(s => ({ ...s, properties: s.properties.filter(p => p.id !== id) }));
      if (userId && sb && !String(id).startsWith('p') && !String(id).startsWith('demo')) {
        await sb.from('biens').delete().eq('id', id);
        // Suppression de la photo liée au bien dans le bucket privé (P0-08)
        if (prop?.photoPath) {
          try { await sb.storage.from('property-photos').remove([prop.photoPath]); } catch (e) {}
        }
      }
    },

    async toggleDoc(id) {
      const newVal = !state.checkedDocs[id];
      const newChecked = { ...state.checkedDocs, [id]: newVal };
      setState(s => ({
        ...s,
        checkedDocs: newChecked,
        documents: computeDocuments(s.profile, newChecked, s.customDocs),
      }));
      if (userId && sb) {
        const doc = ALL_DOCS.find(d => d.id === id) || (state.customDocs || []).find(d => d.id === id);
        await sb.from('checklist_documents').upsert({
          user_id:      userId,
          local_id:     id,
          est_coche:    newVal,
          nom_francais: doc?.name || id,
          destinataire: doc?.recipient || 'perso',
          pays:         'FR',
        }, { onConflict: 'user_id,local_id' });
      }
    },

    /* Lignes de checklist personnalisées (s10q08). */
    async addCustomDoc(name) {
      const clean = (name || '').trim();
      if (!clean) return;
      const exists = (state.customDocs || []).some((d) => d.name.toLowerCase() === clean.toLowerCase());
      if (exists) { toast('Cette ligne existe déjà'); return; }
      const list = [...(state.customDocs || []), { id: 'custom_' + Date.now(), name: clean }];
      setState(s => ({ ...s, customDocs: list, documents: computeDocuments(s.profile, s.checkedDocs, list) }));
      if (userId && sb) {
        const { error } = await sb.from('user_settings').upsert({
          user_id: userId, checklist_custom: list, updated_at: new Date().toISOString(),
        }, { onConflict: 'user_id' });
        if (error) console.error('[immobase] maj checklist perso', error);
      }
    },

    async removeCustomDoc(id) {
      const list = (state.customDocs || []).filter((d) => d.id !== id);
      setState(s => ({ ...s, customDocs: list, documents: computeDocuments(s.profile, s.checkedDocs, list) }));
      if (userId && sb) {
        await sb.from('user_settings').upsert({
          user_id: userId, checklist_custom: list, updated_at: new Date().toISOString(),
        }, { onConflict: 'user_id' });
        await sb.from('checklist_documents').delete().eq('user_id', userId).eq('local_id', id);
      }
    },

    async setProfile(patch) {
      const newProfile = { ...state.profile, ...patch };
      setState(s => ({
        ...s,
        profile: newProfile,
        documents: computeDocuments(newProfile, s.checkedDocs, s.customDocs),
      }));
      if (userId && sb) {
        await sb.from('user_settings').upsert({
          user_id:       userId,
          type_bien:     newProfile.type_bien,
          financement:   newProfile.financement,
          statut_emploi: newProfile.statut_emploi,
          updated_at:    new Date().toISOString(),
        }, { onConflict: 'user_id' });
      }
    },

    async setSim(patch) {
      const newSim = { ...state.sim, ...patch };
      setState(s => ({ ...s, sim: newSim }));
      if (userId && sb) {
        await sb.from('user_settings').upsert({
          user_id:    userId,
          sim_price:  newSim.price,
          sim_apport: newSim.apport,
          sim_duree:  newSim.duree,
          sim_taux:   newSim.taux,
          updated_at: new Date().toISOString(),
        }, { onConflict: 'user_id' });
      }
    },

    async setVisiteCriteres(list) {
      setState(s => ({ ...s, visiteCriteres: list }));
      if (userId && sb) {
        const { error } = await sb.from('user_settings').upsert({
          user_id:               userId,
          visite_criteres_perso: list,
          updated_at:            new Date().toISOString(),
        }, { onConflict: 'user_id' });
        if (error) console.error('[immobase] maj critères perso', error);
      }
    },
  }), [state, userId]);

  return api;
}

function computeLoan({ price, apport, duree, taux }) {
  const principal = Math.max(0, price - apport);
  const n = duree * 12;
  const r = taux / 100 / 12;
  let monthly;
  if (r === 0) monthly = principal / n;
  else monthly = (principal * r) / (1 - Math.pow(1 + r, -n));
  return { principal, monthly, total: monthly * n, interest: monthly * n - principal, n };
}

/* Accessibilité des modales (s11q10) : role=dialog, fermeture Échap,
 * focus initial + piège de focus (Tab reste dans la modale), restitution
 * du focus à la fermeture. À poser sur le conteneur .modal. */
function useModalA11y(onClose) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const prevFocus = document.activeElement;
    el.setAttribute('role', 'dialog');
    el.setAttribute('aria-modal', 'true');
    const focusables = () => Array.from(el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )).filter((n) => !n.disabled && n.offsetParent !== null);
    const first = focusables()[0];
    if (first) first.focus();
    function onKey(e) {
      if (e.key === 'Escape') { e.stopPropagation(); onClose && onClose(); return; }
      if (e.key !== 'Tab') return;
      const list = focusables();
      if (!list.length) return;
      const firstEl = list[0], lastEl = list[list.length - 1];
      if (e.shiftKey && document.activeElement === firstEl) { e.preventDefault(); lastEl.focus(); }
      else if (!e.shiftKey && document.activeElement === lastEl) { e.preventDefault(); firstEl.focus(); }
    }
    document.addEventListener('keydown', onKey, true);
    return () => {
      document.removeEventListener('keydown', onKey, true);
      if (prevFocus && prevFocus.focus) prevFocus.focus();
    };
  }, []);
  return ref;
}

const fmtEur  = (v) => new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(Math.round(v));
const fmtEur2 = (v) => new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 0 }).format(Math.round(v)) + ' €';
const fmtDate = (iso) => {
  if (!iso) return null;
  try { return new Date(iso + 'T00:00').toLocaleDateString('fr-FR', { day: 'numeric', month: 'long' }); }
  catch (e) { return iso; }
};

Object.assign(window, {
  useStore, emptyState, computeLoan, ALL_DOCS, matchesProfile, computeDocuments,
  STATUS_LABELS, STATUS_ORDER, RECIPIENTS, EMAIL_TEMPLATES,
  fmtEur, fmtEur2, fmtDate,
  purgeImmobaseLocal, normalizeStatus, numOrNull, demoProperty, useModalA11y,
});
