// gp-services.jsx — COUCHE DE SERVICES (façades, sans UI)
// ════════════════════════════════════════════════════════════════
//  Point d'accès unique aux données pour TOUS les widgets. Un widget
//  ne touche jamais Odoo, le localStorage ou les tableaux bruts en
//  direct : il passe par un service. On peut changer l'implémentation
//  (Odoo → serveur GO·PASSIF) sans toucher un seul widget.
//
//  Contrat commun à chaque service :
//    { id, label, kind, describe() → { status, lastSync, health, … } }
//
//  Charge après gp-core (utilise loadState/saveState) et gp-data.
// ════════════════════════════════════════════════════════════════
const {
  PRODUCT_SYSTEMS: SVC_SYSTEMS, RECIPES: SVC_RECIPES, recipesForModule: svcRcpFor,
  systemsForModule: svcSysFor, ODOO_CONNECTION: SVC_ODOO_CONN, ODOO_CONTACTS: SVC_ODOO_CONTACTS,
  loadState: svcLoad, saveState: svcSave, SCHEMA_VERSION: SVC_SCHEMA,
  defaultModuleConfig: svcDefMod, emptyProjectData: svcEmptyProj,
  PROJECTS: SVC_PROJECTS, DEFAULT_RECIPE_CHOICES: SVC_DEF_CHOICES,
} = window;

// ── Bus d'événements minimal (découplage inter-services/-widgets) ──
function makeBus() {
  const map = {};
  return {
    on(evt, fn) { (map[evt] = map[evt] || []).push(fn); return () => { map[evt] = map[evt].filter(f => f !== fn); }; },
    emit(evt, payload) { (map[evt] || []).forEach(fn => { try { fn(payload); } catch (e) { console.warn(e); } }); },
  };
}
const bus = makeBus();

// ── odooService : transport vers Odoo (SIMULÉ ici) ────────────────
const odooService = (() => {
  let state = { online: true, lastSync: new Date(Date.now() - 36e5).toISOString(), pending: 0, error: null };
  return {
    id: 'odoo', label: 'Odoo ERP', kind: 'integration',
    conn: SVC_ODOO_CONN,
    describe() {
      return {
        status: state.online ? 'connected' : 'offline',
        lastSync: state.lastSync, health: state.online ? (state.pending ? 'degraded' : 'ok') : 'down',
        pending: state.pending, error: state.error,
        detail: state.online ? `${SVC_ODOO_CONN.server} · ${SVC_ODOO_CONN.db}` : `${SVC_ODOO_CONN.server} injoignable`,
      };
    },
    setOnline(v) { state = { ...state, online: v, error: v ? null : 'Connexion refusée' }; bus.emit('odoo:status', this.describe()); },
    async fetchProducts() {
      if (!state.online) throw new Error('Odoo hors ligne');
      await new Promise(r => setTimeout(r, 300));
      return SVC_SYSTEMS.map(s => ({ odooRef: 'PT-' + s.id, name: s.name, brand: s.brand }));
    },
    async sync() {
      if (!state.online) { state = { ...state, error: 'Synchronisation impossible — hors ligne' }; bus.emit('odoo:status', this.describe()); return false; }
      await new Promise(r => setTimeout(r, 400));
      state = { ...state, lastSync: new Date().toISOString(), pending: 0, error: null };
      bus.emit('odoo:status', this.describe()); bus.emit('catalog:changed');
      return true;
    },
    markPending(n = 1) { state = { ...state, pending: state.pending + n }; bus.emit('odoo:status', this.describe()); },
  };
})();

// ── catalogService : façade PRODUITS / SYSTÈMES / RECETTES ────────
const catalogService = {
  id: 'catalog', label: 'Catalogue produits', kind: 'data',
  _cache: null,
  describe() {
    const od = odooService.describe();
    return {
      status: od.status === 'connected' ? 'ready' : 'cache',
      lastSync: od.lastSync, health: od.health,
      detail: `${SVC_SYSTEMS.length} systèmes · ${SVC_RECIPES.length} recettes`,
      source: od.status === 'connected' ? 'Odoo (live)' : 'cache local',
    };
  },
  systems() { return SVC_SYSTEMS; },
  systemsForModule(id, list) { return svcSysFor(id, list); },
  recipesForModule(id) { return svcRcpFor(id); },
  brandFor(systemId) { const s = SVC_SYSTEMS.find(x => x.id === systemId); return s ? s.brand : null; },
  async refreshFromOdoo() { this._cache = await odooService.fetchProducts(); bus.emit('catalog:changed'); return this._cache; },
};

// ── persistenceService : frontière de stockage ────────────────────
const persistenceService = {
  id: 'persistence', label: 'Stockage local', kind: 'data',
  describe() {
    const s = svcLoad && svcLoad();
    return {
      status: 'ready', health: 'ok',
      lastSync: s && s.savedAt ? new Date(s.savedAt).toISOString() : null,
      detail: `schéma v${SVC_SCHEMA} · navigateur`,
      source: 'localStorage',
    };
  },
  load: svcLoad, save: svcSave,
};

// ── measureService : formes du plan (take-off) ───────────────────
// getState / setState sont injectés par gp-app au démarrage via
// measureService._bind(getState, setState) pour éviter un couplage
// direct entre la couche service et le state React.
const measureService = {
  id: 'measures', label: 'Relevés de plan', kind: 'data',
  _getState: null,
  _setState: null,

  _bind(getState, setState) {
    this._getState = getState;
    this._setState = setState;
  },

  describe() {
    return { status: 'ready', health: 'ok', lastSync: null, detail: 'mesures liées aux widgets', source: 'projet courant' };
  },

  // Retourne les mesures du projet actif
  forProject(pid) {
    if (!this._getState) return [];
    const s = this._getState();
    return (s.byProject[pid] && s.byProject[pid].measures) || [];
  },

  // Exporte les mesures d'un projet au format JSON téléchargeable
  exportJSON(pid, projectName) {
    const measures = this.forProject(pid);
    const payload = {
      schemaVersion: 1,
      exportedAt: new Date().toISOString(),
      projectId: pid,
      projectName: projectName || pid,
      measures,
    };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `mesures-${(projectName || pid).replace(/\s+/g, '-').toLowerCase()}-${new Date().toISOString().slice(0, 10)}.json`;
    a.click();
    URL.revokeObjectURL(url);
  },

  // Importe des mesures depuis un fichier JSON (retourne une Promise<measures[]>)
  importJSON(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = (e) => {
        try {
          const data = JSON.parse(e.target.result);
          if (!data || !Array.isArray(data.measures))
            return reject(new Error('Format invalide — propriété « measures » manquante ou non tableau.'));
          const valid = data.measures.every(m => m && typeof m.id === 'string' && typeof m.name === 'string');
          if (!valid)
            return reject(new Error('Format invalide — une ou plusieurs mesures sont malformées (id ou name manquant).'));
          resolve(data.measures);
        } catch (err) {
          reject(new Error('Fichier JSON illisible : ' + err.message));
        }
      };
      reader.onerror = () => reject(new Error('Erreur de lecture du fichier.'));
      reader.readAsText(file);
    });
  },
};

// ── projectService : création et isolation des données par projet ──
const projectService = {
  id: 'projects', label: 'Projets', kind: 'data',
  describe() { return { status: 'ready', health: 'ok', lastSync: null, detail: 'gestion des projets', source: 'état courant' }; },

  // Initialise byProject[pid] s'il n'existe pas encore.
  ensureProject(pid, state) {
    if (state.byProject && state.byProject[pid]) return state;
    const defaults = svcEmptyProj();
    if (SVC_DEF_CHOICES) {
      Object.entries(SVC_DEF_CHOICES).forEach(([mid, ch]) => {
        defaults.moduleConfig[mid] = { ...svcDefMod(mid), recipe: ch.recipe, brand: ch.brand };
      });
    }
    return {
      ...state,
      byProject: { ...(state.byProject || {}), [pid]: defaults },
    };
  },
};

// ── Registre des services ─────────────────────────────────────────
const GPServices = {
  bus,
  odoo: odooService,
  catalog: catalogService,
  persistence: persistenceService,
  measures: measureService,
  projects: projectService,
  all() { return [catalogService, odooService, persistenceService, measureService]; },
};

Object.assign(window, { GPServices });
