// ─────────────────────────────────────────────────────
// INC-005: pestaña Mapa de Programación. Un marcador por PROYECTO (no por cliente:
// un cliente puede tener varias obras abiertas en sitios distintos), con color por
// el PROCESO que marca la oficina (reabierta, Juanjo 26 oct): pendiente de medida,
// pendiente revisión instalación, remate o garantía; sin proceso = gris.
// Leaflet + teselas OpenStreetMap (cargado en index.html).
// Las coordenadas las calcula el servidor con Nominatim a partir de la dirección
// (calle de la obra, envío o ficha del cliente) o, sin calle, de la población, y las
// guarda en el proyecto. Lo que no se encuentra sale en «Sin ubicar» y se coloca a
// mano pinchando en el mapa o arrastrando el marcador.
// Se carga después de programacion.jsx; usa sus helpers (PG_PROCESOS, PG_ESTADOS…, pgFmt).
// ─────────────────────────────────────────────────────

const PG_MAPA_PROCESOS = [...PG_PROCESOS, ['sin', 'Sin proceso', '#94a3b8']];
const PG_MAPA_COLOR = Object.fromEntries(PG_MAPA_PROCESOS.map(([k, , c]) => [k, c]));
const PG_MAPA_LABEL = Object.fromEntries(PG_MAPA_PROCESOS.map(([k, l]) => [k, l]));
// Búsqueda sin acentos ni mayúsculas.
const pgMapaNorm = (s) => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
const PG_MAPA_CENTRO = [37.39, -5.98]; // Sevilla: sin marcadores, se centra aquí
const pgEsc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));

const pgMapaIcono = (tipo, manual) => L.divIcon({
  className: 'pg-mapa-pin-wrap',
  html: `<span class="pg-mapa-pin${manual ? ' manual' : ''}" style="background:${PG_MAPA_COLOR[tipo] || '#64748b'}"></span>`,
  iconSize: [22, 22], iconAnchor: [11, 22], popupAnchor: [0, -20],
});

// Varias obras en el mismo pueblo comparten coordenadas: se reparten en un círculo
// pequeño para que se puedan pinchar todas (solo visual; no cambia lo guardado).
function pgMapaReparte(list) {
  const grupos = new Map();
  list.forEach((p) => { const k = `${p.geo_lat.toFixed(5)},${p.geo_lng.toFixed(5)}`; if (!grupos.has(k)) grupos.set(k, []); grupos.get(k).push(p); });
  const out = [];
  grupos.forEach((g) => {
    if (g.length === 1) { out.push([g[0], g[0].geo_lat, g[0].geo_lng]); return; }
    g.forEach((p, i) => {
      const ring = Math.floor(i / 8) + 1; const ang = (2 * Math.PI * (i % 8)) / Math.min(8, g.length);
      const r = 0.0035 * ring;
      out.push([p, p.geo_lat + r * Math.sin(ang), p.geo_lng + (r * Math.cos(ang)) / Math.cos((p.geo_lat * Math.PI) / 180)]);
    });
  });
  return out;
}

const PgMapaTab = ({ onToast, tick, onOpen }) => {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [lote, setLote] = React.useState(null);
  const [search, setSearch] = React.useState('');
  const [tipos, setTipos] = React.useState([]); // procesos filtrados
  const [general, setGeneral] = React.useState([]);
  const [estado, setEstado] = React.useState([]);
  const [soloActivos, setSoloActivos] = React.useState(true);
  const [colocando, setColocando] = React.useState(null); // proyecto que se va a colocar con un clic en el mapa
  const user = (API.getUser && API.getUser()) || {};
  const isAdmin = user.rol === 'admin';

  const mapEl = React.useRef(null);
  const map = React.useRef(null);
  const capa = React.useRef(null);
  const encuadrado = React.useRef(false);
  const colocandoRef = React.useRef(null);
  colocandoRef.current = colocando;

  const load = React.useCallback(() => API.get('/api/programacion/mapa')
    .then((r) => { setRows(r.proyectos); setLote(r.lote); setLoading(false); })
    .catch((e) => { onToast(e.message, true); setLoading(false); }), []);
  React.useEffect(() => { load(); }, [load, tick]);

  const guardarPosicion = React.useCallback(async (p, lat, lng) => {
    try {
      await API.put(`/api/programacion/proyectos/${p.id}/posicion`, { lat, lng });
      onToast(`${p.numero} colocado a mano en el mapa`);
    } catch (e) { onToast(e.message, true); }
    load();
  }, [load]);

  // Mapa: se crea una vez. Clic en el mapa = colocar el proyecto elegido en «Sin ubicar».
  React.useEffect(() => {
    if (!window.L || !mapEl.current || map.current) return undefined;
    const m = L.map(mapEl.current, { zoomControl: true }).setView(PG_MAPA_CENTRO, 8);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19, attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
    }).addTo(m);
    capa.current = L.layerGroup().addTo(m);
    m.on('click', (e) => {
      const p = colocandoRef.current;
      if (!p) return;
      setColocando(null);
      guardarPosicion(p, Number(e.latlng.lat.toFixed(6)), Number(e.latlng.lng.toFixed(6)));
    });
    map.current = m;
    setTimeout(() => m.invalidateSize(), 50);
    return () => { m.remove(); map.current = null; capa.current = null; };
  }, []);

  React.useEffect(() => {
    if (map.current) map.current.getContainer().style.cursor = colocando ? 'crosshair' : '';
  }, [colocando]);

  const q = pgMapaNorm(search.trim());
  const filtrados = React.useMemo(() => rows.filter((p) => {
    // «Activos» oculta los terminados, salvo que tengan un proceso (remate, garantía…).
    if (soloActivos && p.estado_general === 'TERMINADO' && p.proceso === 'sin') return false;
    if (tipos.length && !tipos.includes(p.proceso)) return false;
    if (general.length && !general.includes(p.estado_general)) return false;
    if (estado.length && !estado.includes(p.estado || '')) return false;
    if (q && !pgMapaNorm([p.numero, p.nombre, p.cliente_nombre, p.ubicacion, p.provincia, p.obra_direccion, p.cliente_direccion].join(' ')).includes(q)) return false;
    return true;
  }), [rows, soloActivos, tipos, general, estado, q]);
  const ubicados = React.useMemo(() => filtrados.filter((p) => p.geo_lat != null && p.geo_lng != null), [filtrados]);
  const sinUbicar = React.useMemo(() => filtrados.filter((p) => p.geo_lat == null || p.geo_lng == null), [filtrados]);
  const pendientesGeo = rows.filter((p) => !p.geo_origen && (p.ubicacion || p.provincia || p.obra_direccion || p.cliente_direccion)).length;

  // Popup: DOM propio para poder enganchar los botones (abrir, proceso, quitar posición manual).
  const popup = React.useCallback((p) => {
    const el = document.createElement('div');
    el.className = 'pg-mapa-pop';
    const dir = [p.obra_direccion, p.ubicacion, p.provincia].filter(Boolean).join(', ');
    const vacio = p.reclamacion_numero ? `Sin marcar (garantía por ${p.reclamacion_numero})` : 'Sin proceso';
    const procOpts = [['', vacio], ...PG_PROCESOS.map(([k, l]) => [k, l])]
      .map(([k, l]) => `<option value="${k}"${(p.proceso_manual || '') === k ? ' selected' : ''}>${pgEsc(l)}</option>`).join('');
    const posicion = p.geo_origen === 'manual' ? 'colocada a mano' : p.geo_nivel === 'calle' ? 'por la dirección' : 'por la población (sin calle)';
    el.innerHTML = `
      <div class="pg-mapa-pop-h"><span class="mono">${pgEsc(p.numero)}</span> <span class="pg-mapa-dot" style="background:${PG_MAPA_COLOR[p.proceso]}"></span> ${pgEsc(PG_MAPA_LABEL[p.proceso] || p.proceso)}</div>
      <div class="pg-mapa-pop-t">${pgEsc(p.nombre)}</div>
      <dl>
        <dt>Cliente</dt><dd>${pgEsc(p.cliente_nombre || '—')}${p.cliente_telefono ? ` · ${pgEsc(p.cliente_telefono)}` : ''}</dd>
        <dt>Obra</dt><dd>${pgEsc(dir || '—')}</dd>
        ${p.cliente_direccion ? `<dt>Dir. cliente</dt><dd>${pgEsc(p.cliente_direccion)}</dd>` : ''}
        <dt>Estado</dt><dd>${pgEsc([p.estado_general, p.estado].filter(Boolean).join(' · ') || '—')}</dd>
        ${p.proximo_evento_fecha ? `<dt>Próxima fecha</dt><dd>${pgEsc(PG_TIPOS_EVENTO[p.proximo_evento_tipo] || p.proximo_evento_tipo)} · ${pgEsc(pgFmtLong(p.proximo_evento_fecha))}</dd>` : ''}
        ${p.fecha_inicio ? `<dt>Instalación</dt><dd>${pgEsc(pgFmtLong(p.fecha_inicio))}</dd>` : ''}
        ${p.reclamacion_numero ? `<dt>Reclamación</dt><dd>${pgEsc(p.reclamacion_numero)} abierta</dd>` : ''}
        <dt>Proceso</dt><dd><select class="pg-in" data-k="proceso">${procOpts}</select></dd>
        <dt>Posición</dt><dd>${posicion}</dd>
      </dl>
      <div class="pg-mapa-pop-a">
        <button class="btn primary sm" data-k="abrir">Abrir proyecto</button>
        ${p.geo_origen === 'manual' ? '<button class="btn ghost sm" data-k="quitar" title="Volver a la posición calculada con la ubicación del proyecto">Quitar posición manual</button>' : ''}
      </div>
      <div class="muted pg-mapa-pop-n">Arrastra el marcador para corregir su posición.</div>`;
    el.querySelector('[data-k="abrir"]').addEventListener('click', () => onOpen(p.id));
    el.querySelector('[data-k="proceso"]').addEventListener('change', async (e) => {
      try { await API.patch(`/api/programacion/proyectos/${p.id}`, { proceso: e.target.value || null }); load(); }
      catch (err) { onToast(err.message, true); }
    });
    const q2 = el.querySelector('[data-k="quitar"]');
    if (q2) q2.addEventListener('click', async () => {
      try {
        const r = await API.del(`/api/programacion/proyectos/${p.id}/posicion`);
        onToast(r && r.estado === 'ok' ? `Posición recalculada ${r.nivel === 'calle' ? 'con la dirección' : 'con la población'}` : 'Sin posición: queda en «Sin ubicar»');
      } catch (err) { onToast(err.message, true); }
      load();
    });
    return el;
  }, [load, onOpen]);

  // Marcadores: se repintan al cambiar datos o filtros.
  React.useEffect(() => {
    const m = map.current; const g = capa.current;
    if (!m || !g) return;
    g.clearLayers();
    const pts = pgMapaReparte(ubicados).map(([p, lat, lng]) => {
      const mk = L.marker([lat, lng], { icon: pgMapaIcono(p.proceso, p.geo_origen === 'manual'), draggable: true, title: `${p.numero} · ${p.nombre}`, riseOnHover: true });
      mk.bindPopup(() => popup(p), { maxWidth: 320, minWidth: 260 });
      mk.on('dragend', () => {
        const ll = mk.getLatLng();
        if (!confirm(`¿Colocar el proyecto ${p.numero} en esta posición? Quedará fijada a mano y no se recalculará con la dirección.`)) { mk.setLatLng([lat, lng]); return; }
        guardarPosicion(p, Number(ll.lat.toFixed(6)), Number(ll.lng.toFixed(6)));
      });
      mk.addTo(g);
      return [lat, lng];
    });
    if (!encuadrado.current && pts.length) {
      m.fitBounds(L.latLngBounds(pts), { padding: [30, 30], maxZoom: 12 });
      encuadrado.current = true;
    }
  }, [ubicados, popup]);

  // Lote de geocodificación (admin): se lanza en el servidor y aquí se sigue el avance.
  React.useEffect(() => {
    if (!lote || !lote.en_curso) return undefined;
    const t = setInterval(() => {
      API.get('/api/programacion/geocodificacion/estado').then((s) => {
        setLote(s);
        if (!s.en_curso) { clearInterval(t); load(); onToast(`Geocodificación terminada: ${s.ok} ubicados, ${s.fallidos} sin encontrar`); }
      }).catch(() => {});
    }, 3000);
    return () => clearInterval(t);
  }, [lote && lote.en_curso]);
  const lanzarLote = async (reintentar) => {
    try {
      const r = await API.post('/api/programacion/geocodificar-pendientes', { reintentar: !!reintentar });
      setLote(r);
      if (!r.total) onToast('No hay proyectos pendientes de geocodificar');
      else onToast(`Geocodificando ${r.total} proyectos en segundo plano (1 por segundo)…`);
    } catch (e) { onToast(e.message, true); }
  };
  const reintentar = async (p) => {
    try {
      const r = await API.post(`/api/programacion/proyectos/${p.id}/geocodificar`, {});
      onToast(r.estado === 'ok' ? `${p.numero} ubicado ${r.nivel === 'calle' ? 'por la dirección' : 'por la población'}` : r.estado === 'sin_direccion' ? 'Ni el proyecto ni el cliente tienen dirección o población' : `No se encontró «${r.consulta || [p.ubicacion, p.provincia].filter(Boolean).join(', ')}». Corrige la dirección o colócalo a mano.`, r.estado !== 'ok');
    } catch (e) { onToast(e.message, true); }
    load();
  };

  const cuenta = (k) => filtrados.filter((p) => p.proceso === k).length;

  return (
    <>
      <div className="toolbar">
        <div className="tb-search"><Icon name="search" /><input placeholder="Nº, proyecto, cliente, ubicación…" value={search} onChange={(e) => setSearch(e.target.value)} /></div>
        <div className="tb-divider" />
        <MultiChip label="Proceso" options={PG_MAPA_PROCESOS.map(([value, label]) => ({ value, label }))} selected={tipos} onChange={setTipos} />
        <MultiChip label="General" options={PG_ESTADOS_GENERAL.map(([e]) => e)} selected={general} onChange={setGeneral} />
        <MultiChip label="Estado" options={PG_ESTADOS.map(([e]) => e)} selected={estado} onChange={setEstado} />
        <div className="tb-spacer" />
        {isAdmin && (lote && lote.en_curso
          ? <span className="muted" style={{ fontSize: 12 }}>Geocodificando… {lote.hechos}/{lote.total}</span>
          : <button className="btn sm" onClick={() => lanzarLote(false)} title="Calcula en el servidor la posición de los proyectos que aún no la tienen o cuya dirección cambió: por la calle y, si no hay, por la población (1 petición por segundo a OpenStreetMap). No toca los colocados a mano.">
              <Icon name="pin" size={12} /> Geocodificar pendientes{pendientesGeo ? ` (${pendientesGeo})` : ''}
            </button>)}
        <button className="icon-btn" title="Refrescar" onClick={load}><Icon name="bolt" size={13} /></button>
        <div className="seg">
          <button className={soloActivos ? 'on' : ''} onClick={() => setSoloActivos(true)}>Activos</button>
          <button className={!soloActivos ? 'on' : ''} onClick={() => setSoloActivos(false)}>Todos</button>
        </div>
      </div>

      <div className="pg-mapa">
        <div className="pg-mapa-main">
          {colocando && (
            <div className="pg-mapa-colocando">
              <Icon name="pin" size={13} /> Pincha en el mapa donde está la obra de <strong>{colocando.numero}</strong> · {colocando.nombre}
              <button className="btn ghost sm" onClick={() => setColocando(null)}>Cancelar</button>
            </div>
          )}
          <div ref={mapEl} className="pg-mapa-map" />
          <div className="pg-mapa-leyenda">
            {PG_MAPA_PROCESOS.map(([k, l, c]) => (
              <button key={k} className={`pg-mapa-ley ${tipos.length && !tipos.includes(k) ? 'off' : ''}`} title="Mostrar solo este proceso (clic otra vez para quitar el filtro)"
                onClick={() => setTipos((t) => (t.length === 1 && t[0] === k ? [] : [k]))}>
                <span className="pg-mapa-dot" style={{ background: c }} />{l} <span className="muted">{cuenta(k)}</span>
              </button>
            ))}
            <span className="pg-mapa-ley-n"><span className="pg-mapa-dot manual" /> borde discontinuo = colocado a mano</span>
          </div>
        </div>
        <aside className="pg-mapa-side">
          <div className="section-title" style={{ marginTop: 0 }}>Sin ubicar <span className="muted">({sinUbicar.length})</span></div>
          <div className="muted" style={{ fontSize: 11.5, marginBottom: 8 }}>
            Proyectos sin posición (sin dirección ni población, o OpenStreetMap no la encontró). Pulsa «Colocar» y pincha en el mapa.
          </div>
          {loading && <div className="muted">Cargando…</div>}
          {!loading && !sinUbicar.length && <div className="muted" style={{ fontSize: 12 }}>Todos los proyectos filtrados están en el mapa.</div>}
          {sinUbicar.map((p) => (
            <div key={p.id} className={`pg-mapa-item ${colocando && colocando.id === p.id ? 'on' : ''}`}>
              <div className="pg-mapa-item-h">
                <span className="pg-mapa-dot" style={{ background: PG_MAPA_COLOR[p.proceso] }} title={PG_MAPA_LABEL[p.proceso]} />
                <button className="pg-mapa-link mono" onClick={() => onOpen(p.id)} title="Abrir proyecto">{p.numero}</button>
                <span className="pg-mapa-item-n" title={p.nombre}>{p.nombre}</span>
              </div>
              <div className="muted" style={{ fontSize: 11.5 }}>
                {[p.obra_direccion, p.ubicacion, p.provincia].filter(Boolean).join(', ') || p.cliente_direccion || 'Sin dirección'}
                {p.geo_origen === 'fallido' ? ' · no encontrada' : !p.geo_origen && (p.ubicacion || p.provincia || p.obra_direccion || p.cliente_direccion) ? ' · pendiente' : ''}
              </div>
              <div className="pg-mapa-item-a">
                <button className="btn sm" onClick={() => setColocando(p)}><Icon name="pin" size={11} /> Colocar</button>
                {(p.ubicacion || p.provincia || p.obra_direccion || p.cliente_direccion) && <button className="btn ghost sm" onClick={() => reintentar(p)} title="Volver a buscar la dirección (o la población) en OpenStreetMap">Buscar</button>}
              </div>
            </div>
          ))}
        </aside>
      </div>
    </>
  );
};
window.PgMapaTab = PgMapaTab;
