/* global React */
// =====================================================================
// Le Cygne — couche de données partagée (équivalent melr-data.jsx).
// Chargée en <script type="text/babel"> AVANT les écrans. Lit le client
// Supabase posé par le bundle Vite sur window.lecygne.supabase.
// Toute la communication backend passe par window.lecygne.* — aucun
// fetch direct dans les composants d'écran.
// =====================================================================

(function () {
  const NS = (window.lecygne = window.lecygne || {});

  function waitForSupabase() {
    return new Promise((resolve) => {
      const tick = () => {
        const sb = window.lecygne && window.lecygne.supabase;
        if (sb) resolve(sb);
        else setTimeout(tick, 40);
      };
      tick();
    });
  }
  NS.waitForSupabase = waitForSupabase;

  // ---- Format helpers ------------------------------------------------
  NS.fmtAmount = function (v, currency) {
    const n = Number(v) || 0;
    const s = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 0 }).format(n);
    return currency ? `${s} ${currency}` : s;
  };
  NS.fmtDate = function (v) {
    if (!v) return "—";
    try { return new Intl.DateTimeFormat("fr-FR").format(new Date(v)); }
    catch (_) { return v; }
  };

  NS.STATUTS = {
    brouillon:     { fr: "Brouillon",     cls: "gray"  },
    deposee:       { fr: "Déposée",       cls: "blue"  },
    en_traitement: { fr: "En traitement", cls: "amber" },
    prete:         { fr: "Prête",         cls: "teal"  },
    livree:        { fr: "Livrée",        cls: "green" },
    annulee:       { fr: "Annulée",       cls: "red"   },
  };

  // ---- Auth ----------------------------------------------------------
  NS.auth = {
    async signIn(email, password) {
      const sb = await waitForSupabase();
      return sb.auth.signInWithPassword({ email, password });
    },
    async signUp(email, password, fullName) {
      const sb = await waitForSupabase();
      return sb.auth.signUp({ email, password, options: { data: { full_name: fullName } } });
    },
    async signOut() {
      const sb = await waitForSupabase();
      return sb.auth.signOut();
    },
    async getSession() {
      const sb = await waitForSupabase();
      const { data } = await sb.auth.getSession();
      return data.session;
    },
    onChange(cb) {
      let sub = null;
      waitForSupabase().then((sb) => {
        sub = sb.auth.onAuthStateChange((_e, session) => cb(session));
      });
      return () => { try { sub && sub.data.subscription.unsubscribe(); } catch (_) {} };
    },
  };

  // ---- Profil utilisateur courant ------------------------------------
  let _profileCache = undefined;
  NS.getProfile = async function () {
    if (_profileCache !== undefined) return _profileCache;
    const sb = await waitForSupabase();
    const { data: u } = await sb.auth.getUser();
    if (!u || !u.user) { _profileCache = null; return null; }
    const { data } = await sb
      .from("profile")
      .select("id, company_id, full_name, role, is_active, is_super_admin")
      .eq("id", u.user.id)
      .maybeSingle();
    _profileCache = data || null;
    return _profileCache;
  };
  NS._clearProfileCache = () => { _profileCache = undefined; };

  // ---- Société (branding) -------------------------------------------
  NS.company = {
    // Société active : celle du profil si connecté, sinon la 1re active
    // (utile pour afficher le logo sur l'écran de connexion).
    async getActive() {
      const sb = await waitForSupabase();
      const prof = await NS.getProfile();
      let q = sb.from("company").select("*").eq("is_active", true);
      if (prof && prof.company_id) q = q.eq("id", prof.company_id);
      const { data } = await q.order("created_at").limit(1).maybeSingle();
      return data || null;
    },
    async update(id, patch) {
      const sb = await waitForSupabase();
      const allowed = ["display_name", "legal_name", "logo_url", "primary_color",
        "currency", "vat_rate", "locale", "address", "city", "phone", "email", "ninea", "rccm"];
      const clean = {};
      allowed.forEach((k) => { if (k in patch) clean[k] = patch[k]; });
      return sb.from("company").update(clean).eq("id", id).select().maybeSingle();
    },
    async uploadLogo(companyId, file) {
      const sb = await waitForSupabase();
      const ext = (file.name.split(".").pop() || "png").toLowerCase();
      const path = `${companyId}/logo-${Date.now()}.${ext}`;
      const up = await sb.storage.from("company-logos").upload(path, file, { upsert: true });
      if (up.error) return { error: up.error };
      const { data } = sb.storage.from("company-logos").getPublicUrl(path);
      return { url: data.publicUrl };
    },
  };

  // ---- Licence de la société courante --------------------------------
  NS.LICENSE_STATUS = {
    active:    { fr: "Active",    cls: "green" },
    trial:     { fr: "Essai",     cls: "blue"  },
    suspended: { fr: "Suspendue", cls: "amber" },
    expired:   { fr: "Expirée",   cls: "red"   },
  };
  NS.license = {
    async getForCurrent() {
      const sb = await waitForSupabase();
      const prof = await NS.getProfile();
      if (!prof || !prof.company_id) return null;
      const { data } = await sb.from("company_license").select("*")
        .eq("company_id", prof.company_id).maybeSingle();
      return data || null;
    },
    isActive(lic) {
      if (!lic) return false;
      if (!["active", "trial"].includes(lic.status)) return false;
      if (lic.end_date && new Date(lic.end_date) < new Date(new Date().toDateString())) return false;
      return true;
    },
  };

  // ---- Super-administration (transversale aux sociétés) --------------
  NS.superadmin = {
    async listCompanies() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("company")
        .select("*, license:company_license(*)")
        .order("created_at");
      return (data || []).map((c) => ({ ...c, license: Array.isArray(c.license) ? c.license[0] : c.license }));
    },
    async createCompany({ slug, display_name, plan, status, end_date }) {
      const sb = await waitForSupabase();
      return sb.rpc("sa_create_company", {
        p_slug: slug, p_display_name: display_name,
        p_plan: plan || "standard", p_status: status || "trial", p_end_date: end_date || null,
      });
    },
    async setLicense({ company_id, plan, status, start_date, end_date, max_users, notes }) {
      const sb = await waitForSupabase();
      return sb.rpc("sa_set_license", {
        p_company: company_id, p_plan: plan, p_status: status,
        p_start: start_date || null, p_end: end_date || null,
        p_max_users: max_users != null && max_users !== "" ? Number(max_users) : null,
        p_notes: notes || null,
      });
    },
    async listAllUsers() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("profile")
        .select("id, full_name, role, is_active, is_super_admin, company_id")
        .order("created_at");
      return data || [];
    },
    async assignUser({ user_id, company_id, role, is_super_admin }) {
      const sb = await waitForSupabase();
      return sb.rpc("sa_assign_user", {
        p_user: user_id, p_company: company_id,
        p_role: role || "comptoir", p_super: !!is_super_admin,
      });
    },
  };

  // ---- CRUD générique sur tables company-scopées ---------------------
  function withCompany(row, companyId) { return { ...row, company_id: companyId }; }

  NS.clients = {
    async list({ search } = {}) {
      const sb = await waitForSupabase();
      let q = sb.from("client").select("*").order("societe", { nullsFirst: false });
      if (search) q = q.or(`societe.ilike.%${search}%,nom_client.ilike.%${search}%`);
      const { data } = await q.limit(500);
      return data || [];
    },
    async get(code) {
      const sb = await waitForSupabase();
      const { data } = await sb.from("client").select("*").eq("code_client", code).maybeSingle();
      return data;
    },
    async upsert(row, companyId) {
      const sb = await waitForSupabase();
      return sb.from("client").upsert(withCompany(row, companyId)).select().maybeSingle();
    },
    async remove(code) {
      const sb = await waitForSupabase();
      return sb.from("client").delete().eq("code_client", code);
    },
    // Historique : commandes du client + solde dû (somme des restes à payer)
    async history(code) {
      const sb = await waitForSupabase();
      const cmds = await sb.from("commande")
        .select("no_commande, date_commande, statut, no_facture")
        .eq("code_client", code).order("no_commande", { ascending: false }).limit(200);
      const facs = await sb.from("facture")
        .select("montant_ttc, statut, paiements:paiement(montant)")
        .eq("code_client", code).in("statut", ["emise", "partiellement_payee"]);
      let solde = 0;
      (facs.data || []).forEach((f) => {
        const paye = (f.paiements || []).reduce((s, p) => s + Number(p.montant || 0), 0);
        solde += Number(f.montant_ttc || 0) - paye;
      });
      return { commandes: cmds.data || [], solde };
    },
  };

  NS.articles = {
    async list() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("article").select("*").order("ref_article");
      return data || [];
    },
    async upsert(row, companyId) {
      const sb = await waitForSupabase();
      return sb.from("article").upsert(withCompany(row, companyId)).select().maybeSingle();
    },
  };

  NS.services = {
    async list() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("service").select("*").order("code_service");
      return data || [];
    },
    async upsert(row, companyId) {
      const sb = await waitForSupabase();
      return sb.from("service").upsert(withCompany(row, companyId)).select().maybeSingle();
    },
  };

  NS.tarifs = {
    async list() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("tarif").select("*").is("date_fin", null).order("ref_article");
      return data || [];
    },
    async upsert(row, companyId) {
      const sb = await waitForSupabase();
      // Clé naturelle (article × service × date d'effet) : on met à jour le
      // prix du jour s'il existe déjà, sinon on insère une nouvelle ligne.
      return sb.from("tarif")
        .upsert(withCompany(row, companyId), { onConflict: "ref_article,code_service,date_effet" })
        .select().maybeSingle();
    },
    async remove(id) {
      const sb = await waitForSupabase();
      return sb.from("tarif").delete().eq("id", id);
    },
    // Prix courant pour un couple article × service
    async price(refArticle, codeService) {
      const sb = await waitForSupabase();
      const { data } = await sb.from("tarif").select("prix_unitaire")
        .eq("ref_article", refArticle).eq("code_service", codeService)
        .is("date_fin", null).order("date_effet", { ascending: false }).limit(1).maybeSingle();
      return data ? Number(data.prix_unitaire) : 0;
    },
  };

  NS.commandes = {
    async list({ statut, search } = {}) {
      const sb = await waitForSupabase();
      let q = sb.from("commande")
        .select("*, client:client(code_client, societe, nom_client)")
        .order("no_commande", { ascending: false });
      if (statut) q = q.eq("statut", statut);
      const { data } = await q.limit(500);
      let rows = data || [];
      if (search) {
        const s = search.toLowerCase();
        rows = rows.filter((r) =>
          String(r.no_commande).includes(s) ||
          ((r.client && (r.client.societe || r.client.nom_client) || "").toLowerCase().includes(s)));
      }
      return rows;
    },
    async get(no) {
      const sb = await waitForSupabase();
      const { data } = await sb.from("commande")
        .select("*, client:client(*), lignes:ligne_commande(*, article:article(nom_article), service:service(nom_service))")
        .eq("no_commande", no).maybeSingle();
      return data;
    },
    // Toutes les commandes rattachées à un n° de facture (facture multi-commandes)
    async byFacture(noFacture) {
      const sb = await waitForSupabase();
      const { data } = await sb.from("commande")
        .select("*, lignes:ligne_commande(*, article:article(nom_article), service:service(nom_service))")
        .eq("no_facture", noFacture);
      return data || [];
    },
    async nextNo() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("commande").select("no_commande")
        .order("no_commande", { ascending: false }).limit(1).maybeSingle();
      return data ? data.no_commande + 1 : 1;
    },
    async upsert(row, companyId) {
      const sb = await waitForSupabase();
      return sb.from("commande").upsert(withCompany(row, companyId)).select().maybeSingle();
    },
    async setStatut(no, statut) {
      const sb = await waitForSupabase();
      return sb.from("commande").update({ statut }).eq("no_commande", no);
    },
    async updateHeader(no, patch) {
      const sb = await waitForSupabase();
      const allowed = ["code_client", "date_commande", "a_livrer_avant", "statut", "port",
        "destinataire", "adresse_livraison", "ville_livraison"];
      const clean = {};
      allowed.forEach((k) => { if (k in patch) clean[k] = patch[k]; });
      return sb.from("commande").update(clean).eq("no_commande", no);
    },
    async replaceLignes(no, lignes) {
      const sb = await waitForSupabase();
      await sb.from("ligne_commande").delete().eq("no_commande", no);
      if (!lignes.length) return { data: [] };
      const rows = lignes.map((l) => ({
        no_commande: no, ref_article: l.ref_article, code_service: l.code_service,
        quantite: l.quantite, prix_unitaire: l.prix_unitaire,
      }));
      return sb.from("ligne_commande").insert(rows).select();
    },
  };

  NS.MODES_PAIEMENT = ["especes", "cheque", "virement", "mobile"];
  NS.STATUTS_FACTURE = {
    emise:               { fr: "Émise",            cls: "blue"  },
    partiellement_payee: { fr: "Partiellement payée", cls: "amber" },
    payee:               { fr: "Payée",            cls: "green" },
    annulee:             { fr: "Annulée",          cls: "red"   },
  };

  // ---- Factures & paiements ------------------------------------------
  NS.factures = {
    async list({ statut, search } = {}) {
      const sb = await waitForSupabase();
      let q = sb.from("facture")
        .select("*, client:client(code_client, societe, nom_client), paiements:paiement(montant)")
        .order("date_facture", { ascending: false });
      if (statut) q = q.eq("statut", statut);
      const { data } = await q.limit(500);
      let rows = (data || []).map((f) => {
        const paye = (f.paiements || []).reduce((s, p) => s + Number(p.montant || 0), 0);
        return { ...f, deja_paye: paye, reste_a_payer: Number(f.montant_ttc || 0) - paye };
      });
      if (search) {
        const s = search.toLowerCase();
        rows = rows.filter((f) => (f.no_facture || "").toLowerCase().includes(s) ||
          ((f.client && (f.client.societe || f.client.nom_client) || "").toLowerCase().includes(s)));
      }
      return rows;
    },
    async get(id) {
      const sb = await waitForSupabase();
      const { data } = await sb.from("facture")
        .select("*, client:client(*), paiements:paiement(*)")
        .eq("id", id).maybeSingle();
      return data;
    },
    // Génère UNE facture regroupant une ou plusieurs commandes du MÊME client.
    async generateFromCommandes(noList, company) {
      const sb = await waitForSupabase();
      if (!noList || !noList.length) return { error: { message: "Aucune commande sélectionnée." } };
      const details = [];
      for (const no of noList) { const c = await NS.commandes.get(no); if (c) details.push(c); }
      if (!details.length) return { error: { message: "Commandes introuvables." } };
      const client = details[0].code_client;
      if (details.some((d) => d.code_client !== client))
        return { error: { message: "Toutes les commandes doivent appartenir au même client." } };
      const already = details.filter((d) => d.no_facture).map((d) => "#" + d.no_commande);
      if (already.length) return { error: { message: "Déjà facturé : " + already.join(", ") } };
      const ht = details.reduce((s, d) =>
        s + (d.lignes || []).reduce((a, l) => a + Number(l.montant || 0), 0) + Number(d.port || 0), 0);
      const taux = Number((company && company.vat_rate) != null ? company.vat_rate : 18);
      const ttc = ht * (1 + taux / 100);
      const nos = noList.slice().sort((a, b) => a - b);
      const no_facture = "FAC-" + nos[0] + (nos.length > 1 ? "G" + nos.length : "");
      const ins = await sb.from("facture").insert({
        company_id: company.id, no_facture, no_commande: nos[0], code_client: client,
        montant_ht: ht, taux_tva: taux, montant_ttc: ttc, statut: "emise",
      }).select().maybeSingle();
      if (ins.error) return ins;
      await sb.from("commande").update({ no_facture }).in("no_commande", nos);
      return ins;
    },
    // Compat : 1 commande → 1 facture
    async generateFromCommande(no, company) {
      return this.generateFromCommandes([no], company);
    },
    // Impayés regroupés par client, pour les relances
    async relances() {
      const sb = await waitForSupabase();
      const { data } = await sb.from("facture")
        .select("*, client:client(*), paiements:paiement(montant)")
        .in("statut", ["emise", "partiellement_payee"]);
      const groups = {};
      (data || []).forEach((f) => {
        const paye = (f.paiements || []).reduce((s, p) => s + Number(p.montant || 0), 0);
        const reste = Number(f.montant_ttc || 0) - paye;
        if (reste <= 0) return;
        const code = f.code_client;
        if (!groups[code]) groups[code] = { client: f.client, factures: [], total: 0 };
        groups[code].factures.push({ ...f, deja_paye: paye, reste_a_payer: reste });
        groups[code].total += reste;
      });
      return Object.values(groups).sort((a, b) => b.total - a.total);
    },
    async addPayment(factureId, montant, mode) {
      const sb = await waitForSupabase();
      const ins = await sb.from("paiement").insert({
        facture_id: factureId, montant: Number(montant), mode: mode || "especes",
      });
      if (ins.error) return ins;
      // Recalcule le statut selon le reste à payer
      const f = await this.get(factureId);
      const paye = (f.paiements || []).reduce((s, p) => s + Number(p.montant || 0), 0);
      const statut = paye <= 0 ? "emise" : (paye >= Number(f.montant_ttc) ? "payee" : "partiellement_payee");
      await sb.from("facture").update({ statut }).eq("id", factureId);
      return { data: { statut } };
    },
  };

  // ---- Journal d'audit -----------------------------------------------
  NS.AUDIT_TABLES = {
    client: "Clients", commande: "Commandes", ligne_commande: "Lignes",
    facture: "Factures", paiement: "Paiements", tarif: "Tarifs",
    service: "Services", article: "Articles", company: "Société",
    company_license: "Licences", profile: "Utilisateurs",
  };
  NS.audit = {
    async list({ table, limit } = {}) {
      const sb = await waitForSupabase();
      let q = sb.from("audit_log").select("*").order("changed_at", { ascending: false }).limit(limit || 200);
      if (table) q = q.eq("table_name", table);
      const { data } = await q;
      return data || [];
    },
  };
  NS.useAudit = (opts) => useAsync(() => NS.audit.list(opts || {}), [opts && opts.table]);

  // ---- Stats tableau de bord -----------------------------------------
  NS.stats = async function () {
    const sb = await waitForSupabase();
    const now = new Date();
    const firstDay = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);
    const enCours = await sb.from("commande").select("no_commande", { count: "exact", head: true })
      .in("statut", ["deposee", "en_traitement", "prete"]);
    const clients = await sb.from("client").select("code_client", { count: "exact", head: true });
    const aLivrer = await sb.from("commande").select("no_commande", { count: "exact", head: true })
      .in("statut", ["deposee", "en_traitement", "prete"]).not("a_livrer_avant", "is", null);
    const factures = await sb.from("facture")
      .select("montant_ttc, statut, date_facture, paiements:paiement(montant)").limit(2000);
    let caMonth = 0, impayes = 0, nbImpayees = 0;
    (factures.data || []).forEach((f) => {
      if (f.date_facture && f.date_facture >= firstDay) caMonth += Number(f.montant_ttc || 0);
      const paye = (f.paiements || []).reduce((s, p) => s + Number(p.montant || 0), 0);
      const reste = Number(f.montant_ttc || 0) - paye;
      if (["emise", "partiellement_payee"].includes(f.statut) && reste > 0) { impayes += reste; nbImpayees++; }
    });
    return {
      enCours: enCours.count || 0,
      clients: clients.count || 0,
      aLivrer: aLivrer.count || 0,
      caMonth, impayes, nbImpayees,
    };
  };

  // ---- CA par mois (n derniers mois) pour le graphique --------------
  NS.revenueByMonth = async function (n) {
    n = n || 6;
    const sb = await waitForSupabase();
    const now = new Date();
    const buckets = [];
    for (let i = n - 1; i >= 0; i--) {
      const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
      buckets.push({
        key: d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0"),
        label: new Intl.DateTimeFormat("fr-FR", { month: "short" }).format(d).replace(".", ""),
        value: 0,
      });
    }
    const from = buckets[0].key + "-01";
    const { data } = await sb.from("facture")
      .select("montant_ttc, date_facture").gte("date_facture", from).limit(5000);
    (data || []).forEach((f) => {
      const k = (f.date_facture || "").slice(0, 7);
      const b = buckets.find((x) => x.key === k);
      if (b) b.value += Number(f.montant_ttc || 0);
    });
    return buckets;
  };

  // ---- Recherche globale (palette Ctrl/Cmd+K) -----------------------
  NS.globalSearch = async function (term) {
    const t = (term || "").trim();
    if (t.length < 2) return [];
    const sb = await waitForSupabase();
    const out = [];
    const cl = await sb.from("client").select("code_client, societe, nom_client")
      .or(`societe.ilike.%${t}%,nom_client.ilike.%${t}%`).limit(6);
    (cl.data || []).forEach((c) => out.push({
      type: "client", route: "clients", label: c.societe || c.nom_client,
      sub: "Client #" + c.code_client, term: c.societe || c.nom_client,
    }));
    if (/^\d+$/.test(t)) {
      const cm = await sb.from("commande").select("no_commande, statut").eq("no_commande", Number(t)).limit(1);
      (cm.data || []).forEach((c) => out.push({
        type: "commande", route: "commandes", label: "Commande #" + c.no_commande,
        sub: (NS.STATUTS[c.statut] || {}).fr || c.statut, term: String(c.no_commande),
      }));
    }
    const fc = await sb.from("facture").select("no_facture, statut")
      .ilike("no_facture", `%${t}%`).limit(5);
    (fc.data || []).forEach((f) => out.push({
      type: "facture", route: "factures", label: f.no_facture,
      sub: (NS.STATUTS_FACTURE[f.statut] || {}).fr || f.statut, term: f.no_facture,
    }));
    return out;
  };

  // ---- Hooks React ---------------------------------------------------
  function useAsync(fn, deps) {
    const [state, setState] = React.useState({ loading: true, data: null, error: null });
    const reload = React.useCallback(() => {
      let alive = true;
      setState((s) => ({ ...s, loading: true }));
      Promise.resolve(fn()).then(
        (data) => { if (alive) setState({ loading: false, data, error: null }); },
        (error) => { if (alive) setState({ loading: false, data: null, error }); }
      );
      return () => { alive = false; };
    }, deps); // eslint-disable-line
    React.useEffect(reload, [reload]);
    return { ...state, reload };
  }
  NS.useAsync = useAsync;

  NS.useCompany = () => useAsync(() => NS.company.getActive(), []);
  NS.useProfile = () => useAsync(() => NS.getProfile(), []);
  NS.useClients = (opts) => useAsync(() => NS.clients.list(opts || {}), [opts && opts.search]);
  NS.useCommandes = (opts) => useAsync(() => NS.commandes.list(opts || {}), [opts && opts.statut, opts && opts.search]);
  NS.useArticles = () => useAsync(() => NS.articles.list(), []);
  NS.useServices = () => useAsync(() => NS.services.list(), []);
  NS.useTarifs = () => useAsync(() => NS.tarifs.list(), []);
  NS.useStats = () => useAsync(() => NS.stats(), []);
  NS.useFactures = (opts) => useAsync(() => NS.factures.list(opts || {}), [opts && opts.statut, opts && opts.search]);
  NS.useRevenue = () => useAsync(() => NS.revenueByMonth(6), []);
  NS.useLicense = () => useAsync(() => NS.license.getForCurrent(), []);
  NS.useCompaniesAdmin = () => useAsync(() => NS.superadmin.listCompanies(), []);
  NS.useAllUsers = () => useAsync(() => NS.superadmin.listAllUsers(), []);
})();
