/* ===== admin-import-ags.jsx — import a COMPLETE legacy project (AGS4 or CSV mall) =====
   Legacy-import: reconstructs a whole project (project record + boreholes with
   coordinates + full strata log) from one of two sources, auto-detected:
     · Tier 1 — AGS4 package (.ags): PROJ + LOCA + GEOL (+ SAMP/ISPT/WSTG)
     · Tier 2 — the RP Geolog CSV mall (.csv, ';'-delimited): one row per layer
       (see "Andmete ettevalmistus.html"). Material lives in the "Täpne materjal"
       column (legend code) and/or "Välimaterjal" (one of the 12 field types);
       water levels in "Vesi ilmus"/"Veetase".
   Both formats parse to ONE common shape, then share the mapping + review +
   commit UI below.

   Real-world quirks handled (seen in GL24049_Tulbad.ags):
     · European decimal COMMA in every number ("0,05", "531161,85")
     · groups in ANY order (GEOL before LOCA)
     · coords as L-EST97 in LOCA_NATE/NATN (+ LOCA_GL); NO WGS84 → converted
     · LOCA_FDEP is a bottom *elevation*, not a depth → depth derived from the
       deepest GEOL_BASE instead
     · material carried as a legend code in GEOL_GEOL ("A - saCl", "Muld",
       "JämepurdmoreenD - clGr"…) defined in ABBR → mapped to the precise
       61-material library via heuristic + an editable mapping table. */

/* ---- reverse map: precise material id → simplified field type ---- */
const MAT_TO_FIELD = (function () {
  const m = {};
  FIELD_MATERIALS.forEach((f) => (f.refines || []).forEach((mid) => { if (!m[mid]) m[mid] = f.id; }));
  return m;
})();

/* "0,05" → 0.05 ; "" → NaN */
function agsNum(v) {
  if (v == null) return NaN;
  return parseFloat(String(v).trim().replace(/\s/g, "").replace(",", "."));
}

/* one AGS/CSV line → array of fields (handles "" escapes) */
function parseAgsLine(line) {
  const out = [];
  let i = 0, cur = "", inQ = false;
  while (i < line.length) {
    const c = line[i];
    if (inQ) {
      if (c === '"') { if (line[i + 1] === '"') { cur += '"'; i += 2; continue; } inQ = false; i++; continue; }
      cur += c; i++;
    } else {
      if (c === '"') { inQ = true; i++; }
      else if (c === ",") { out.push(cur); cur = ""; i++; }
      else { cur += c; i++; }
    }
  }
  out.push(cur);
  return out;
}

/* full AGS4 text → structured groups { NAME: { headings:[], rows:[{HEAD:val}] } } */
function parseAgsGroups(text) {
  const groups = {};
  let cur = null, headings = null;
  String(text).split(/\r?\n/).forEach((raw) => {
    const line = raw.trim();
    if (!line) return;
    const cells = parseAgsLine(line);
    const kind = cells[0];
    if (kind === "GROUP") { cur = cells[1]; groups[cur] = { headings: [], rows: [] }; headings = null; }
    else if (kind === "HEADING" && cur) { headings = cells.slice(1); groups[cur].headings = headings; }
    else if (kind === "DATA" && cur && headings) {
      const o = {};
      headings.forEach((h, i) => { o[h] = cells[i + 1] !== undefined ? cells[i + 1] : ""; });
      groups[cur].rows.push(o);
    }
  });
  return groups;
}

/* map a legend / "Täpne materjal" string → precise material id (best guess; "" = unknown) */
function guessAgsMat(raw) {
  const s = String(raw || "").trim();
  if (!s) return "";
  const l = s.toLowerCase();
  if (l.includes("geotekstiil")) return "m65";
  if (l.includes("asfalt") || l.includes("mustkate")) return "m5";
  if (l.includes("täide") || l.includes("täitepinnas") || l.includes("täitemat")) return ""; // made ground → field type only
  if (l.includes("munakivi")) return "m61";
  if (l.includes("paekillustik") || l.includes("lubjakivikillustik")) return "m33";
  if (l.includes("killustik")) return "m33";
  if (l.includes("sõelmed")) return "m35";
  if (l.includes("murenenud lubjakivi")) return "m63";
  if (l.includes("lubjakivi") || l.includes("paas")) return "m62";
  if (l.includes("dolomiit")) return "m64";
  if (l.includes("suurrahn")) return "m59";
  if (l.includes("rahn")) return "m60";
  if (l.includes("veeris")) return "m61";
  if (l.includes("turbamuda")) return "m58";
  if (l.includes("turvas") || l.includes("turba")) return "m54";
  if (l.includes("järvemuda") || l.includes("muda")) return "m57";
  if (l.includes("kasvupinnas") || l.includes("kasvukiht")) return "m56";
  if (l.includes("muld")) return "m55";
  if (l.includes("kruusliiv") || l.includes("kruus")) return "m41";

  /* isolate the ISO 14688 symbol: drop genesis words + "A - " / "D - " horizon prefix */
  const moraine = /moreen/i.test(s);
  let sym = s.replace(/jämepurdmoreen/ig, " ").replace(/peenpurdmoreen/ig, " ").replace(/moreen/ig, " ").replace(/paesõelmed/ig, " ");
  sym = sym.replace(/^\s*[a-zA-Z]\s*[-–]\s*/, "");     // "A - saCl" / "D - ClM" → drop horizon letter
  sym = sym.replace(/.*[-–]\s*/, "").trim();            // any remaining genesis tag
  sym = sym.replace(/([a-z])[A-Z]+$/, "$1");           // trailing genesis caps: "ClM" → "Cl"
  if (!sym) return "";

  let principal = "";
  for (let i = 0; i < sym.length; i++) if (sym[i] >= "A" && sym[i] <= "Z") principal = sym.slice(i);
  if (!principal) return "";
  const p = principal.toLowerCase();
  const mods = sym.slice(0, sym.length - principal.length).toLowerCase();

  if (p.startsWith("cl")) return "m51";                 // clay → liivsavi/savi
  if (p.startsWith("si")) return "m50";                 // silt → tolmliiv
  if (p.startsWith("gr") || p === "g") return moraine ? "m37" : "m41"; // gravel
  if (p.startsWith("sa") || p === "s") {               // sand — refine by grain / fines
    if (mods.includes("gr")) return "m41";             // gravelly sand → kruusliiv
    if (mods.includes("cl")) return "m49";             // clayey sand → saviliiv
    if (mods.indexOf("f") === 0) return "m45";         // fine sand → peenliiv
    if (mods.indexOf("c") === 0) return "m42";         // coarse sand → jämeliiv
    return "m43";                                      // medium sand default
  }
  return "";
}

/* map a free-text material name → simplified FIELD type id ("" = unknown) */
function guessFieldType(raw) {
  const l = String(raw || "").trim().toLowerCase();
  if (!l) return "";
  for (const f of FIELD_MATERIALS) if (l === f.name.toLowerCase()) return f.id;
  if (l.includes("täide") || l.includes("täitepinnas") || l.includes("täitemat")) return "f_taide";
  if (l.includes("asfalt") || l.includes("mustkate")) return "f_asfalt";
  if (l.includes("killustik")) return "f_killustik";
  if (l.includes("kruus")) return "f_kruus";
  if (l.includes("munakivi") || l.includes("rahn") || l.includes("veeris") || l.includes("lubjakivi") || l.includes("dolomiit") || l.includes("paas")) return "f_kivim";
  if (l.includes("liivsavi")) return "f_liivsavi";
  if (l.includes("saviliiv")) return "f_saviliiv";
  if (l.includes("savi")) return "f_savi";
  if (l.includes("möll") || l.includes("moll")) return "f_moll";
  if (l.includes("liiv")) return "f_liiv";
  if (l.includes("turvas") || l.includes("muda")) return "f_turvas";
  if (l.includes("muld") || l.includes("kasvupinnas")) return "f_kasvupinnas";
  return "";
}

/* derive a short project name (lühinimi) from a long descriptive name */
function shortProjName(name, code) {
  if (!name) return code || "";
  let s = name.replace(/^riigitee\s+[\d.\/]+\s+/i, "");   // drop "Riigitee 23136 "
  s = s.split(/\s+km\s+/i)[0];                            // drop "km 9,159 …"
  s = s.trim();
  return s || name.split(/[\s,]+/)[0] || code || "";
}

/* AGS type code → system borehole type (BH/TP/CP) */
function agsHoleType(t) {
  const u = String(t || "").toUpperCase();
  if (u === "TP" || u === "TRIALPIT") return "TP";
  if (u === "CP" || u === "CPT") return "CP";
  return "BH"; // PA (puurauk), RC, etc.
}

/* parse AGS text → { ok, error, version, proj, transDate, holes[], codes[] } */
function parseAGS(text) {
  if (!/("|^)GROUP("|,)/m.test(text) && !/"GROUP"/.test(text)) {
    return { ok: false, error: "See ei ole AGS-fail (GROUP-ridu ei leitud)." };
  }
  const g = parseAgsGroups(text);
  if (!g.PROJ || !g.PROJ.rows.length) return { ok: false, error: "AGS-failist puudub PROJ-grupp (projekti andmed)." };
  if (!g.GEOL && !g.LOCA) return { ok: false, error: "AGS-failist puudub LOCA ja GEOL grupp." };

  const pr = g.PROJ.rows[0];
  const proj = { code: (pr.PROJ_ID || "").trim(), name: (pr.PROJ_NAME || "").trim(), client: (pr.PROJ_CLNT || "").trim() };
  const version = g.TRAN && g.TRAN.rows[0] ? (g.TRAN.rows[0].TRAN_AGS || "4.0") : "4.x";
  const transDate = g.TRAN && g.TRAN.rows[0] ? (g.TRAN.rows[0].TRAN_DATE || "") : "";

  /* LOCA → coordinate / metadata index by id */
  const loca = {};
  if (g.LOCA) g.LOCA.rows.forEach((r) => {
    const id = (r.LOCA_ID || "").trim();
    if (!id) return;
    loca[id] = {
      type: agsHoleType(r.LOCA_TYPE),
      north: agsNum(r.LOCA_NATN), east: agsNum(r.LOCA_NATE),
      gl: agsNum(r.LOCA_GL),
      rem: (r.LOCA_REM || "").trim(),
    };
  });

  /* GEOL → strata grouped by hole, in depth order */
  const strata = {};
  const codeSet = {};
  if (g.GEOL) g.GEOL.rows.forEach((r) => {
    const id = (r.LOCA_ID || "").trim();
    if (!id) return;
    const top = agsNum(r.GEOL_TOP), base = agsNum(r.GEOL_BASE);
    const code = (r.GEOL_GEOL || r.GEOL_LEG || "").trim();
    if (code) codeSet[code] = (codeSet[code] || 0) + 1;
    (strata[id] = strata[id] || []).push({ top, base, code });
  });
  Object.keys(strata).forEach((id) => strata[id].sort((a, b) => a.top - b.top));

  /* union of hole ids from LOCA + GEOL */
  const ids = Array.from(new Set(Object.keys(loca).concat(Object.keys(strata))));
  ids.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));

  const holes = ids.map((id) => {
    const lc = loca[id] || {};
    const layers = strata[id] || [];
    const depth = layers.length ? Math.max.apply(null, layers.map((x) => x.base || 0)) : 0;
    const warns = [];
    const hasCoord = isFinite(lc.north) && isFinite(lc.east) && lc.north > 6000000 && lc.east > 100000;
    if (!hasCoord) warns.push("koordinaadita");
    if (!layers.length) warns.push("kihtideta");
    // depth continuity
    for (let i = 1; i < layers.length; i++) {
      if (Math.abs((layers[i].top || 0) - (layers[i - 1].base || 0)) > 0.001) { warns.push("kihtide vahe"); break; }
    }
    return {
      id, type: lc.type || "BH",
      north: lc.north, east: lc.east, gl: lc.gl,
      depth, layers, warns, hasCoord,
    };
  });

  return { ok: true, version, proj, transDate, holes, codes: Object.keys(codeSet).sort((a, b) => a.localeCompare(b)) };
}

/* one CSV line → fields, honoring "" quoting and a configurable delimiter */
function splitCsvLine(line, delim) {
  const out = [];
  let i = 0, cur = "", inQ = false;
  while (i < line.length) {
    const c = line[i];
    if (inQ) {
      if (c === '"') { if (line[i + 1] === '"') { cur += '"'; i += 2; continue; } inQ = false; i++; continue; }
      cur += c; i++;
    } else {
      if (c === '"') { inQ = true; i++; }
      else if (c === delim) { out.push(cur); cur = ""; i++; }
      else { cur += c; i++; }
    }
  }
  out.push(cur);
  return out;
}

/* parse the RP Geolog CSV mall → same shape as parseAGS (one row per layer) */
function parseTemplateCSV(text) {
  const raw = String(text).replace(/^\ufeff/, "");
  const lines = raw.split(/\r?\n/).filter((l) => l.trim().length);
  if (lines.length < 2) return { ok: false, error: "Tühi või päiseta CSV-fail." };

  const delim = lines[0].includes(";") ? ";" : (lines[0].includes("\t") ? "\t" : ",");
  const headers = splitCsvLine(lines[0], delim).map((h) => h.trim().toLowerCase());
  const find = (preds) => headers.findIndex((h) => preds.some((p) => h.includes(p)));
  const col = {
    code: find(["projekti kood", "proj_id"]),
    name: find(["projekti nimi", "proj_name"]),
    client: find(["tellija", "clnt"]),
    id: find(["puuraugu id", "loca_id", "augu id"]),
    type: find(["tüüp", "tuup", "type"]),
    north: headers.findIndex((h) => h.includes("põhi") || h.includes("pohi") || h.includes("natn") || h.includes("(n)") || /^x\b/.test(h) || h.startsWith("x ")),
    east: headers.findIndex((h) => h.includes("ida") || h.includes("nate") || h.includes("(e)") || /^y\b/.test(h) || h.startsWith("y ")),
    gl: find(["maapinna", "kõrgus", "korgus", "loca_gl"]),
    wStrike: find(["vesi ilmus", "ilmus"]),
    wLevel: find(["veetase"]),
    top: find(["ülemine", "ulemine", "geol_top"]),
    base: find(["alumine", "geol_base"]),
    fieldMat: find(["välimaterjal", "valimaterjal"]),
    preciseMat: find(["täpne materjal", "tapne materjal", "geol_geol"]),
    desc: find(["kirjeldus", "märkus", "markus"]),
  };
  if (col.code < 0 || col.id < 0 || col.top < 0 || col.base < 0) {
    return { ok: false, error: "CSV päises puuduvad kohustuslikud veerud (Projekti kood, Puuraugu ID, Kihi ülemine/alumine). Kasuta malli „Andmete ettevalmistus”." };
  }
  const get = (cells, c) => (c >= 0 && c < cells.length ? cells[c].trim() : "");

  const first = splitCsvLine(lines[1], delim);
  const proj = { code: get(first, col.code), name: get(first, col.name), client: get(first, col.client) };

  const byId = {}; const order = []; const codeSet = {}; const other = {};
  for (let i = 1; i < lines.length; i++) {
    const cells = splitCsvLine(lines[i], delim);
    const pc = get(cells, col.code);
    if (pc && proj.code && pc.toUpperCase() !== proj.code.toUpperCase()) { other[pc] = 1; continue; }
    const hid = get(cells, col.id);
    if (!hid) continue;
    if (!byId[hid]) {
      byId[hid] = {
        id: hid, type: agsHoleType(get(cells, col.type) || "PA"),
        north: agsNum(get(cells, col.north)), east: agsNum(get(cells, col.east)), gl: agsNum(get(cells, col.gl)),
        veeStrike: agsNum(get(cells, col.wStrike)), veetase: agsNum(get(cells, col.wLevel)),
        layers: [],
      };
      order.push(hid);
    }
    const top = agsNum(get(cells, col.top)), base = agsNum(get(cells, col.base));
    if (!isFinite(top) && !isFinite(base)) continue;          // not a layer row
    const precise = get(cells, col.preciseMat);
    const fieldRaw = get(cells, col.fieldMat);
    const code = precise || fieldRaw;
    if (code) codeSet[code] = (codeSet[code] || 0) + 1;
    byId[hid].layers.push({ top, base, code, desc: get(cells, col.desc), fieldDirect: guessFieldType(fieldRaw) || guessFieldType(precise) });
  }

  const holes = order.map((id) => {
    const h = byId[id];
    h.layers.sort((a, b) => (a.top || 0) - (b.top || 0));
    h.depth = h.layers.length ? Math.max.apply(null, h.layers.map((x) => x.base || 0)) : 0;
    h.hasCoord = isFinite(h.north) && isFinite(h.east) && h.north > 6000000 && h.east > 100000;
    const warns = [];
    if (!h.hasCoord) warns.push("koordinaadita");
    if (!h.layers.length) warns.push("kihtideta");
    for (let i = 1; i < h.layers.length; i++) {
      if (Math.abs((h.layers[i].top || 0) - (h.layers[i - 1].base || 0)) > 0.001) { warns.push("kihtide vahe"); break; }
    }
    h.warns = warns;
    return h;
  });

  const res = { ok: true, format: "csv", version: "mall", proj, transDate: "", holes, codes: Object.keys(codeSet).sort((a, b) => a.localeCompare(b)) };
  const others = Object.keys(other);
  if (others.length) res.otherProjects = others;
  return res;
}

/* ---------- material <select> (grouped, with "ootel") ---------- */
function MatSelect({ value, onChange }) {
  return (
    <select className="ags-matsel" value={value || ""} onChange={(e) => onChange(e.target.value)}>
      <option value="">— ootel (täpsustamata) —</option>
      {MATERIAL_GROUPS.map((grp) => (
        <optgroup key={grp} label={grp}>
          {MATERIALS.filter((m) => m.group === grp).map((m) => (
            <option key={m.id} value={m.id}>{m.code}{m.desc ? " · " + m.desc : ""}</option>
          ))}
        </optgroup>
      ))}
    </select>
  );
}

/* ---------- drawer ---------- */
function AgsImportDrawer({ onClose, onDone }) {
  const [parsed, setParsed] = useState(null);   // result of parseAGS + fileName
  const [map, setMap] = useState({});           // legend code -> matId
  const [holeOn, setHoleOn] = useState({});      // id -> bool
  const [analysed, setAnalysed] = useState(true);
  const [over, setOver] = useState(false);
  const [busy, setBusy] = useState(false);
  const fileRef = useRef(null);

  const handleText = (name, text) => {
    const looksAgs = /"GROUP"/.test(text) || /\.ags$/i.test(name);
    const p = looksAgs ? parseAGS(text) : parseTemplateCSV(text);
    p.fileName = name;
    if (p.ok) {
      const m = {}; p.codes.forEach((c) => { m[c] = guessAgsMat(c); });
      const on = {}; p.holes.forEach((h) => { on[h.id] = h.hasCoord && h.layers.length > 0; });
      setMap(m); setHoleOn(on);
    }
    setParsed(p);
  };
  const handleFile = (f) => {
    if (!f) return;
    const rd = new FileReader();
    rd.onload = () => handleText(f.name, String(rd.result));
    rd.readAsText(f);
  };

  const ok = parsed && parsed.ok;
  const existing = ok ? (window.PROJECTS || []).find((p) => (p.code || "").toUpperCase() === parsed.proj.code.toUpperCase()) : null;
  const holes = ok ? parsed.holes : [];
  const picked = holes.filter((h) => holeOn[h.id]);
  const totLayers = picked.reduce((s, h) => s + h.layers.length, 0);
  const unmapped = ok ? parsed.codes.filter((c) => !map[c]).length : 0;

  const doImport = async () => {
    if (!ok || !picked.length) return;
    setBusy(true);
    const f = parsed.proj;
    const short = shortProjName(f.name, f.code);
    let pid = existing ? existing.id : null;
    pid = await RPStore.upsertProject({ code: f.code, name: f.name, short, client: f.client, status: "Active" }, pid);

    for (const h of picked) {
      const geos = h.layers.map((L) => {
        const mat = map[L.code] || "";
        const fieldMat = (mat && MAT_TO_FIELD[mat]) || L.fieldDirect || "";
        return {
          top: (L.top || 0).toFixed(2), base: (L.base || 0).toFixed(2),
          desc: (L.desc && L.desc.length) ? L.desc : L.code, fieldMat, mat,
        };
      });
      const loca = {
        id: h.id, type: h.type, depth: h.depth ? h.depth.toFixed(2) : "",
        start: parsed.transDate || "", end: "",
        east: isFinite(h.east) ? h.east.toFixed(2) : "", north: isFinite(h.north) ? h.north.toFixed(2) : "",
        gl: isFinite(h.gl) ? h.gl.toFixed(2) : "", lat: "", lon: "",
        veetase: isFinite(h.veetase) ? h.veetase.toFixed(2) : "", veeStrike: isFinite(h.veeStrike) ? h.veeStrike.toFixed(2) : "",
      };
      await RPStore.saveBoreholeLog(pid, { loca, geos, samp: [], ispt: [], photos: [] });
      if (analysed && geos.length && geos.every((x) => x.mat)) {
        await RPStore.setLayerMats(h.id, geos.map((x) => x.mat), {});
      }
    }
    setBusy(false);
    onDone({ pid, code: f.code, holes: picked.length, layers: totLayers, isNew: !existing });
  };

  return (
    <div className="scrim" onClick={busy ? undefined : onClose}>
      <div className="drawer drawer-ags" onClick={(e) => e.stopPropagation()}>
        <div className="drawer-head">
          <div>
            <h2>Impordi projekt</h2>
            <p>Tervikliku vana projekti import · AGS4 (.ags) või CSV mall (.csv)</p>
          </div>
          <button className="iconbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>

        <div className="drawer-body">
          <div className={"impdrop" + (over ? " over" : "")}
            onClick={() => fileRef.current && fileRef.current.click()}
            onDragOver={(e) => { e.preventDefault(); setOver(true); }}
            onDragLeave={() => setOver(false)}
            onDrop={(e) => { e.preventDefault(); setOver(false); handleFile(e.dataTransfer.files[0]); }}>
            <Icon name="cloud" size={26} />
            <b>{parsed ? parsed.fileName : "Lohista .ags või .csv fail siia või klõpsa"}</b>
            <span className="mono">AGS4 või RP Geolog CSV mall · L-EST97 koordinaadid</span>
          </div>
          <input ref={fileRef} type="file" accept=".ags,.csv,.txt" style={{ display: "none" }}
            onChange={(e) => { handleFile(e.target.files[0]); e.target.value = ""; }} />

          {parsed && !parsed.ok && <div className="imp-error">{parsed.error}</div>}

          {ok && (
            <React.Fragment>
              {/* project card */}
              <div className="ags-proj">
                <div className="ags-proj-top">
                  <span className="mono ags-proj-code">{parsed.proj.code || "—"}</span>
                  {existing
                    ? <span className="badge amber"><span className="dot"></span>Projekt olemas — täiendatakse</span>
                    : <span className="badge green"><span className="dot"></span>Uus projekt</span>}
                  <span className="badge slate"><span className="dot"></span>{parsed.format === "csv" ? "CSV mall" : "AGS " + parsed.version}</span>
                </div>
                <div className="ags-proj-name">{parsed.proj.name || "(nimetu)"}</div>
                <div className="ags-proj-meta mono">{parsed.proj.client ? "Tellija: " + parsed.proj.client : "Tellija puudub"}{parsed.transDate ? " · " + parsed.transDate : ""}</div>
              </div>

              <div className="ags-stats">
                <div className="ags-stat"><b>{holes.length}</b><span>puurauku</span></div>
                <div className="ags-stat"><b>{parsed.holes.reduce((s, h) => s + h.layers.length, 0)}</b><span>kihti</span></div>
                <div className="ags-stat"><b>{parsed.codes.length}</b><span>materjalikoodi</span></div>
                <div className={"ags-stat" + (unmapped ? " warn" : "")}><b>{unmapped}</b><span>sidumata</span></div>
              </div>

              {parsed.otherProjects && <div className="imp-error">Fail sisaldab ka teisi projekte ({parsed.otherProjects.join(", ")}) — imporditakse ainult {parsed.proj.code}. Impordi need eraldi.</div>}

              {/* material mapping */}
              <div className="ags-sec-h">
                <h3>Materjalide sidumine</h3>
                <span className="ags-sec-sub">{parsed.format === "csv" ? "„Täpne materjal” → täpsed materjalid. Kontrolli ja korrigeeri." : "AGS legendikoodid → täpsed materjalid. Kontrolli ja korrigeeri."}</span>
              </div>
              <table className="ags-maptbl">
                <thead><tr><th>Kood</th><th>Esineb</th><th>Seotud materjal</th><th></th></tr></thead>
                <tbody>
                  {parsed.codes.map((c) => {
                    const mat = map[c] ? getMat(map[c]) : null;
                    const n = parsed.holes.reduce((s, h) => s + h.layers.filter((L) => L.code === c).length, 0);
                    return (
                      <tr key={c} className={map[c] ? "" : "unmapped"}>
                        <td><span className="mono ags-code">{c}</span></td>
                        <td className="mono ags-n">{n}×</td>
                        <td><MatSelect value={map[c]} onChange={(v) => setMap((s) => ({ ...s, [c]: v }))} /></td>
                        <td>{mat ? <span className="ags-sw" style={{ background: mat.hex }} title={mat.code}></span> : <span className="ags-sw hatched" title="ootel"></span>}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>

              {/* boreholes */}
              <div className="ags-sec-h">
                <h3>Puuraugud</h3>
                <span className="ags-sec-sub">{picked.length} / {holes.length} valitud</span>
              </div>
              <table className="ags-bhtbl">
                <thead><tr><th></th><th>ID</th><th>Tüüp</th><th>L-EST97 X / Y</th><th>Sügavus</th><th>Kihte</th><th>Vesi</th><th>Märkus</th></tr></thead>
                <tbody>
                  {holes.map((h) => (
                    <tr key={h.id} className={holeOn[h.id] ? "" : "off"} onClick={() => setHoleOn((s) => ({ ...s, [h.id]: !s[h.id] }))}>
                      <td><input type="checkbox" checked={!!holeOn[h.id]} readOnly disabled={!h.layers.length} /></td>
                      <td><span className="mono strong">{h.id}</span></td>
                      <td><span className={"kindtag" + (h.type === "TP" ? " tp" : h.type === "CP" ? " cp" : "")}>{h.type}</span></td>
                      <td className="mono">{h.hasCoord ? h.north.toFixed(1) + " / " + h.east.toFixed(1) : "—"}</td>
                      <td className="mono">{h.depth ? h.depth.toFixed(2) + " m" : "—"}</td>
                      <td className="mono">{h.layers.length}</td>
                      <td className="mono">{isFinite(h.veeStrike) ? h.veeStrike.toFixed(1) + " m" : "—"}</td>
                      <td>{h.warns.length ? <span className="ags-warn">{h.warns.join(" · ")}</span> : <span className="ags-ok">ok</span>}</td>
                    </tr>
                  ))}
                </tbody>
              </table>

              <label className="imp-opt">
                <input type="checkbox" checked={analysed} onChange={(e) => setAnalysed(e.target.checked)} />
                Märgi täielikult seotud augud analüüsituks (etapp <b>Analüüs</b>)
              </label>
              <p className="imp-note">Imporditakse projekt, puuraukude koordinaadid (L-EST97 → WGS84) ja kogu kihiline läbilõige. Sidumata materjaliga kihid jäävad <b>ootele</b>.</p>
            </React.Fragment>
          )}
        </div>

        <div className="drawer-foot">
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>Tühista</button>
          <button className="btn btn-primary" disabled={!ok || !picked.length || busy} onClick={doImport}>
            <Icon name="check" size={16} /> {busy ? "Impordin…" : "Impordi " + picked.length + " puurauku"}
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { parseAGS, parseAgsGroups, parseTemplateCSV, guessAgsMat, guessFieldType, shortProjName, AgsImportDrawer, MAT_TO_FIELD });
