/* eslint-disable */
/*
 * TapMapPage -- Partner-only map of Colorado commercial water taps.
 *
 * Data flow:
 *   - Loads tap_provider_summary() once (sidebar metadata + total counts).
 *   - On map move/zoom and on filter change, calls taps_in_bbox(...) RPC.
 *   - Same query feeds the synced sortable table on the right.
 *   - Export button paginates through the same filter to download the
 *     entire matching set as XLSX (or CSV).
 *
 * Auth: all RPC calls require the user to be authenticated AND have
 *   partners.tap_map_access = true (enforced server-side via RLS).
 */

const TAP_MAP_DEFAULT_CENTER = [39.55, -105.78];   // CO center
const TAP_MAP_DEFAULT_ZOOM = 7;
const TAP_MAP_PAGE_LIMIT = 5000;       // server-side cap per request
const TAP_MAP_EXPORT_CAP = 50000;      // sanity cap so a runaway export doesn't OOM the tab

function tapDebounce(fn, ms) {
  let t;
  return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
}

function tapFmtDate(d) {
  if (!d) return '';
  const s = String(d);
  return s.length >= 10 ? s.slice(0, 10) : s;
}

function tapFmtSize(v) {
  if (v == null) return '';
  if (v >= 1) return `${(+v).toFixed(2).replace(/\.00$/, '')}"`;
  // sub-inch sizes round to common fractions
  const map = { 0.5: '1/2"', 0.625: '5/8"', 0.75: '3/4"' };
  return map[+v] || `${v}"`;
}

function TapMapFilters({
  providers, filters, setFilters, summary, lastLoaded,
}) {
  const totalCommercial = summary.reduce((a, p) => a + (p.commercial_taps || 0), 0);
  const totalTaps = summary.reduce((a, p) => a + (p.total_taps || 0), 0);

  return (
    <aside className="tapmap-side">
      <div className="tapmap-side__head">
        <div className="ccg-eyebrow">Filters</div>
        <h3 className="tapmap-side__title">Tap Map</h3>
        <div className="tapmap-side__sub">
          {totalCommercial.toLocaleString()} commercial of {totalTaps.toLocaleString()} taps
          {lastLoaded ? <> &middot; updated {tapFmtDate(lastLoaded)}</> : null}
        </div>
      </div>

      <div className="tapmap-field">
        <label className="ccg-label">Address contains</label>
        <input
          className="ccg-input"
          type="search"
          placeholder="e.g. HARMONY"
          value={filters.address}
          onChange={(e) => setFilters(f => ({ ...f, address: e.target.value }))}
        />
      </div>

      <div className="tapmap-field">
        <label className="ccg-label">Providers</label>
        <div className="tapmap-checks">
          {providers.map(p => (
            <label key={p.provider_id} className="tapmap-check">
              <input
                type="checkbox"
                checked={filters.providerIds.includes(p.provider_id)}
                onChange={(e) => setFilters(f => ({
                  ...f,
                  providerIds: e.target.checked
                    ? [...f.providerIds, p.provider_id]
                    : f.providerIds.filter(x => x !== p.provider_id),
                }))}
              />
              <span>{p.name}</span>
              <span className="tapmap-check__count">{(p.commercial_taps || 0).toLocaleString()}</span>
            </label>
          ))}
          {providers.length === 0
            ? <div className="tapmap-empty">No providers loaded yet.</div>
            : null}
        </div>
      </div>

      <div className="tapmap-row">
        <div className="tapmap-field tapmap-field--half">
          <label className="ccg-label">Min meter (in)</label>
          <input
            className="ccg-input" type="number" step="0.25" min="0"
            value={filters.minMeter ?? ''}
            onChange={(e) => setFilters(f => ({
              ...f, minMeter: e.target.value === '' ? null : +e.target.value
            }))}
          />
        </div>
        <div className="tapmap-field tapmap-field--half">
          <label className="ccg-label">Max meter (in)</label>
          <input
            className="ccg-input" type="number" step="0.25" min="0"
            value={filters.maxMeter ?? ''}
            onChange={(e) => setFilters(f => ({
              ...f, maxMeter: e.target.value === '' ? null : +e.target.value
            }))}
          />
        </div>
      </div>

      <div className="tapmap-field">
        <label className="tapmap-check">
          <input
            type="checkbox"
            checked={filters.commercialOnly}
            onChange={(e) => setFilters(f => ({ ...f, commercialOnly: e.target.checked }))}
          />
          <span>Commercial only (≥ 1")</span>
        </label>
      </div>

      <div className="tapmap-field">
        <label className="ccg-label">Status</label>
        <select
          className="ccg-input"
          value={filters.status || ''}
          onChange={(e) => setFilters(f => ({ ...f, status: e.target.value || null }))}
        >
          <option value="">Any</option>
          <option>Active</option>
          <option>Inactive</option>
          <option>Abandoned</option>
          <option>Unknown</option>
        </select>
      </div>
    </aside>
  );
}

function TapMapTable({ rows, totalCount, sort, setSort, onRowHover, onRowClick }) {
  const COLS = [
    { key: 'meter_size_in', label: 'Size',     w: 70,  fmt: tapFmtSize },
    { key: 'full_address',  label: 'Address',  w: 280 },
    { key: 'provider_name', label: 'Provider', w: 180 },
    { key: 'is_commercial', label: 'Comm.',    w: 70,  fmt: v => v ? 'Yes' : 'No' },
    { key: 'status',        label: 'Status',   w: 90 },
    { key: 'install_date',  label: 'Installed',w: 100, fmt: tapFmtDate },
    { key: 'account_id',    label: 'Account',  w: 120 },
  ];

  // Client-side sort within the page; server handles the global ordering
  const sorted = React.useMemo(() => {
    if (!sort) return rows;
    const dir = sort.dir === 'desc' ? -1 : 1;
    return [...rows].sort((a, b) => {
      const av = a[sort.key], bv = b[sort.key];
      if (av == null && bv == null) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;
      return String(av).localeCompare(String(bv)) * dir;
    });
  }, [rows, sort]);

  function head(c) {
    const active = sort && sort.key === c.key;
    const arrow = active ? (sort.dir === 'desc' ? ' ▼' : ' ▲') : '';
    return (
      <th
        key={c.key}
        style={{ width: c.w }}
        onClick={() => setSort(prev =>
          prev && prev.key === c.key
            ? { key: c.key, dir: prev.dir === 'desc' ? 'asc' : 'desc' }
            : { key: c.key, dir: 'desc' }
        )}
      >
        {c.label}{arrow}
      </th>
    );
  }

  return (
    <div className="tapmap-table-wrap">
      <div className="tapmap-table-head">
        Showing <strong>{rows.length.toLocaleString()}</strong> of <strong>{totalCount.toLocaleString()}</strong> matching taps
        {totalCount > rows.length
          ? <> &middot; <span className="tapmap-table-head__hint">zoom in or refine filters to see more</span></>
          : null}
      </div>
      <div className="tapmap-table-scroll">
        <table className="tapmap-table">
          <thead><tr>{COLS.map(head)}</tr></thead>
          <tbody>
            {sorted.map(r => (
              <tr
                key={r.tap_uid}
                onMouseEnter={() => onRowHover && onRowHover(r)}
                onMouseLeave={() => onRowHover && onRowHover(null)}
                onClick={() => onRowClick && onRowClick(r)}
              >
                {COLS.map(c => (
                  <td key={c.key}>{c.fmt ? c.fmt(r[c.key]) : (r[c.key] ?? '')}</td>
                ))}
              </tr>
            ))}
            {sorted.length === 0
              ? <tr><td className="tapmap-table-empty" colSpan={COLS.length}>No matching taps in this view.</td></tr>
              : null}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function TapMapPage({ session, onSignOut }) {
  const mapRef = React.useRef(null);
  const mapElRef = React.useRef(null);
  const clusterRef = React.useRef(null);
  const hoverHighlightRef = React.useRef(null);

  const [providers, setProviders] = React.useState([]);
  const [providerLastLoaded, setProviderLastLoaded] = React.useState(null);
  const [authzError, setAuthzError] = React.useState(null);

  const [filters, setFilters] = React.useState({
    providerIds: [],
    minMeter: null,
    maxMeter: null,
    commercialOnly: true,
    status: null,
    address: '',
  });

  const [rows, setRows] = React.useState([]);
  const [totalCount, setTotalCount] = React.useState(0);
  const [bbox, setBbox] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [exporting, setExporting] = React.useState(false);
  const [sort, setSort] = React.useState({ key: 'meter_size_in', dir: 'desc' });

  // ---- 1. Initialize Leaflet map (once) ---------------------------------
  React.useEffect(() => {
    if (!mapElRef.current || mapRef.current) return;
    if (!window.L) { console.warn('Leaflet not loaded'); return; }
    const L = window.L;
    const map = L.map(mapElRef.current, {
      preferCanvas: true,
    }).setView(TAP_MAP_DEFAULT_CENTER, TAP_MAP_DEFAULT_ZOOM);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '&copy; OpenStreetMap',
      maxZoom: 19,
    }).addTo(map);
    const cluster = L.markerClusterGroup({
      chunkedLoading: true,
      disableClusteringAtZoom: 17,
      maxClusterRadius: 50,
    });
    cluster.addTo(map);
    mapRef.current = map;
    clusterRef.current = cluster;

    function pushBbox() {
      const b = map.getBounds();
      setBbox({
        north: b.getNorth(), south: b.getSouth(),
        east: b.getEast(),   west: b.getWest(),
      });
    }
    pushBbox();
    map.on('moveend zoomend', tapDebounce(pushBbox, 250));

    return () => { map.off(); map.remove(); mapRef.current = null; };
  }, []);

  // ---- 2. Load provider summary (once per session) ----------------------
  React.useEffect(() => {
    if (!session || !window.supabaseClient) return;
    let cancelled = false;
    (async () => {
      const { data, error } = await window.supabaseClient.rpc('tap_provider_summary');
      if (cancelled) return;
      if (error) {
        // Most common cause: tap_map_access = false on this partner row.
        if (/permission|access|RLS|denied/i.test(error.message)) {
          setAuthzError('Your account does not have Tap Map access yet. Contact Ross to enable it.');
        } else if (/function .* does not exist/i.test(error.message)) {
          setAuthzError('Tap Map database is not yet provisioned. The schema migration has not been applied.');
        } else {
          setAuthzError(error.message);
        }
        setProviders([]);
        return;
      }
      setProviders(data || []);
      const newest = (data || []).reduce((m, r) => (r.last_loaded_at && (!m || r.last_loaded_at > m)) ? r.last_loaded_at : m, null);
      setProviderLastLoaded(newest);
    })();
    return () => { cancelled = true; };
  }, [session && session.user.id]);

  // ---- 3. Run bbox query whenever bbox or filters change ---------------
  const runQuery = React.useMemo(() => tapDebounce(async (b, f) => {
    if (!b || !window.supabaseClient) return;
    setBusy(true);
    const params = {
      p_north: b.north, p_south: b.south, p_east: b.east, p_west: b.west,
      p_provider_ids: f.providerIds.length ? f.providerIds : null,
      p_min_meter_in: f.minMeter,
      p_max_meter_in: f.maxMeter,
      p_commercial:   f.commercialOnly ? true : null,
      p_status:       f.status,
      p_address_q:    f.address.trim() || null,
      p_limit:        TAP_MAP_PAGE_LIMIT,
      p_offset:       0,
    };
    const { data, error } = await window.supabaseClient.rpc('taps_in_bbox', params);
    setBusy(false);
    if (error) { console.warn('taps_in_bbox', error); return; }
    setRows(data || []);
    setTotalCount(data && data.length ? data[0].total_count : 0);
  }, 250), []);

  React.useEffect(() => { runQuery(bbox, filters); }, [bbox, filters]);

  // ---- 4. Sync markers to map on rows change ---------------------------
  React.useEffect(() => {
    const cluster = clusterRef.current;
    const L = window.L;
    if (!cluster || !L) return;
    cluster.clearLayers();
    const markers = [];
    for (const r of rows) {
      if (r.latitude == null || r.longitude == null) continue;
      const marker = L.circleMarker([r.latitude, r.longitude], {
        radius: r.meter_size_in >= 4 ? 8 : r.meter_size_in >= 2 ? 6 : 5,
        weight: 1,
        color: '#1A1A1A',
        fillColor: r.is_commercial ? '#5B9B3E' : '#8a9ba1',
        fillOpacity: 0.8,
      });
      marker.bindPopup(
        `<strong>${r.full_address || '(no address)'}</strong><br>` +
        `${r.provider_name}<br>` +
        `Meter: ${tapFmtSize(r.meter_size_in)} &middot; ${r.status || 'Unknown'}<br>` +
        (r.account_id ? `Account: ${r.account_id}<br>` : '') +
        (r.install_date ? `Installed: ${tapFmtDate(r.install_date)}` : '')
      );
      marker._tapUid = r.tap_uid;
      markers.push(marker);
    }
    cluster.addLayers(markers);
  }, [rows]);

  // ---- 5. Hover-highlight a row on the map -----------------------------
  function highlightOnMap(r) {
    const L = window.L;
    if (!L || !mapRef.current) return;
    if (hoverHighlightRef.current) {
      hoverHighlightRef.current.remove();
      hoverHighlightRef.current = null;
    }
    if (r && r.latitude != null && r.longitude != null) {
      hoverHighlightRef.current = L.circleMarker([r.latitude, r.longitude], {
        radius: 14, weight: 3, color: '#000', fillColor: '#FFD700', fillOpacity: 0.6,
      }).addTo(mapRef.current);
    }
  }

  function flyToRow(r) {
    if (mapRef.current && r && r.latitude != null && r.longitude != null) {
      mapRef.current.setView([r.latitude, r.longitude], Math.max(mapRef.current.getZoom(), 17));
    }
  }

  // ---- 6. Export the full filtered set as XLSX -------------------------
  async function exportAll(format) {
    if (!bbox || !window.supabaseClient || !window.XLSX) return;
    setExporting(true);
    try {
      const all = [];
      let offset = 0;
      // Use the *current* bbox + filters; page through until we hit total or the cap.
      while (offset < TAP_MAP_EXPORT_CAP) {
        const { data, error } = await window.supabaseClient.rpc('taps_in_bbox', {
          p_north: bbox.north, p_south: bbox.south, p_east: bbox.east, p_west: bbox.west,
          p_provider_ids: filters.providerIds.length ? filters.providerIds : null,
          p_min_meter_in: filters.minMeter,
          p_max_meter_in: filters.maxMeter,
          p_commercial:   filters.commercialOnly ? true : null,
          p_status:       filters.status,
          p_address_q:    filters.address.trim() || null,
          p_limit:        TAP_MAP_PAGE_LIMIT,
          p_offset:       offset,
        });
        if (error) { alert('Export error: ' + error.message); break; }
        if (!data || data.length === 0) break;
        all.push(...data);
        const total = data[0].total_count;
        if (all.length >= total) break;
        offset += data.length;
      }
      // Strip total_count from each row for the export
      const cleaned = all.map(({ total_count, ...rest }) => ({
        ...rest,
        meter_size_in: rest.meter_size_in ?? null,
        install_date: tapFmtDate(rest.install_date),
      }));
      const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
      const fname = `tap_map_export_${stamp}`;
      if (format === 'csv') {
        const ws = XLSX.utils.json_to_sheet(cleaned);
        const csv = XLSX.utils.sheet_to_csv(ws);
        const blob = new Blob([csv], { type: 'text/csv' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = fname + '.csv';
        a.click();
        URL.revokeObjectURL(a.href);
      } else {
        const wb = XLSX.utils.book_new();
        const ws = XLSX.utils.json_to_sheet(cleaned);
        XLSX.utils.book_append_sheet(wb, ws, 'Taps');
        XLSX.writeFile(wb, fname + '.xlsx');
      }
    } finally {
      setExporting(false);
    }
  }

  // ---- Render ---------------------------------------------------------
  if (!session) {
    return (
      <section className="ccg-section ccg-login">
        <div className="ccg-login__card">
          <h2 className="ccg-login__title">Tap Map</h2>
          <p>Please <a href="#login">sign in</a> to view the Tap Map.</p>
        </div>
      </section>
    );
  }

  return (
    <section className="tapmap">
      <div className="tapmap__head">
        <div>
          <div className="ccg-eyebrow">Partner Tools</div>
          <h2 className="tapmap__title">Colorado Tap Map</h2>
          <hr className="ccg-rule-accent" />
        </div>
        <div className="tapmap__actions">
          <button
            className="ccg-btn ccg-btn--ghost"
            onClick={() => exportAll('csv')}
            disabled={exporting || rows.length === 0}
            title="Download the current filtered set as CSV"
          >{exporting ? 'Exporting…' : 'Export CSV'}</button>
          <button
            className="ccg-btn ccg-btn--primary"
            onClick={() => exportAll('xlsx')}
            disabled={exporting || rows.length === 0}
            title="Download the current filtered set as Excel (.xlsx)"
          >{exporting ? 'Exporting…' : 'Export Excel'}</button>
        </div>
      </div>

      {authzError ? (
        <div className="tapmap__notice">{authzError}</div>
      ) : null}

      <div className="tapmap__layout">
        <TapMapFilters
          providers={providers}
          filters={filters}
          setFilters={setFilters}
          summary={providers}
          lastLoaded={providerLastLoaded}
        />
        <div className="tapmap__center">
          <div ref={mapElRef} className="tapmap__map" />
          {busy ? <div className="tapmap__busy">Loading…</div> : null}
        </div>
        <div className="tapmap__right">
          <TapMapTable
            rows={rows}
            totalCount={totalCount}
            sort={sort}
            setSort={setSort}
            onRowHover={highlightOnMap}
            onRowClick={flyToRow}
          />
        </div>
      </div>
    </section>
  );
}
window.TapMapPage = TapMapPage;
