// search.js — search box logic (pure): coordinates, H3 cell ids and pin matches → suggestions
import { filterPins } from './pins.js';
import { fmtLatLon } from './units.js';
const HEMI = { N: ['lat', 1], S: ['lat', -1], E: ['lon', 1], W: ['lon', -1] };
const MARKS = ['°', "'", '"'];
function tokens(q) {
return q.toUpperCase()
.replace(/[°º]/g, ' ° ').replace(/['′’]/g, " ' ").replace(/["″”]/g, ' " ')
.replace(/([NSEW])/g, ' $1 ').replace(/[,;]/g, ' , ')
.split(/\s+/).filter(Boolean);
}
export function parseCoords(q) { // "lat,lon" first; failing that, commas between digits are decimal commas
return parseCoordsAs(q) ?? (/\d,\d/.test(q) ? parseCoordsAs(q.replace(/(\d),(\d)/g, '$1.$2')) : null);
}
function parseCoordsAs(q) {
const comps = [];
let cur = null;
const close = () => { if (cur?.parts.length) comps.push(cur); cur = null; };
const toks = tokens(q);
for (let i = 0; i < toks.length; i++) {
const t = toks[i];
if (t === ',') { close(); continue; }
if (HEMI[t]) {
if (cur?.parts.length && !cur.hemi) { cur.hemi = t; close(); }
else { close(); cur = { parts: [], units: [], hemi: t }; }
continue;
}
if (!/^[+-]?\d+(\.\d+)?$/.test(t)) return null;
const mark = MARKS.includes(toks[i + 1]) ? MARKS.indexOf(toks[++i]) : null;
if (cur?.parts.length && (mark === null || mark <= cur.units.at(-1))) close(); // bare number or restarting unit
cur ??= { parts: [], units: [], hemi: null };
cur.parts.push(parseFloat(t));
cur.units.push(mark ?? 0);
}
close();
if (comps.length !== 2 || comps.some(c => c.parts.slice(1).some(v => v < 0 || v >= 60))) return null;
const val = c => c.parts.reduce((a, v, k) => a + Math.abs(v) / 60 ** c.units[k], 0) * (c.parts[0] < 0 ? -1 : 1);
const out = {};
const lettered = comps.filter(c => c.hemi);
for (const c of lettered) {
const [axis, sign] = HEMI[c.hemi];
if (axis in out) return null;
out[axis] = Math.abs(val(c)) * sign;
}
const bare = comps.filter(c => !c.hemi);
for (const c of bare) out['lat' in out ? 'lon' : 'lat'] = val(c);
if (!(Math.abs(out.lat) <= 90) || !(Math.abs(out.lon) <= 180)) return null;
return { lat: out.lat, lon: out.lon };
}
export const isCellId = q => /^8[0-9a-f]{14}$/i.test(q.trim());
export function suggest(q, pins, max = 8) {
const s = q.trim();
if (!s) return [];
const out = [];
const c = parseCoords(s);
if (c) out.push({ kind: 'coords', label: fmtLatLon(c.lat, c.lon), ...c });
if (isCellId(s)) out.push({ kind: 'cell', label: `H3 cell ${s.toLowerCase()}`, cell: s.toLowerCase() });
for (const p of filterPins(pins, s).slice(0, Math.max(0, max - out.length))) {
out.push({ kind: 'pin', label: p.name, sub: p.lore || fmtLatLon(p.lat, p.lon), lat: p.lat, lon: p.lon });
}
return out;
}