/ ===== 植物选型库 + 苗木配置表(单价默认留空,支持导入价格清单) ===== /
window.PlantUI = (function () {

/ ---------- 品种名规范化:用于价格清单匹配 ---------- /
function normName(s) {

return String(s || "")
  .replace(/[((].*?[))]/g, "")   // 去括号注释
  .replace(/[\s\u3000]/g, "")      // 去空白
  .replace(/[·・.。,,'"'"]/g, "")
  .toLowerCase();

}

/* 从价格清单取价:完全匹配 → 规格同时匹配优先 /
function lookupPrice(name, spec) {

const key = normName(name);
const hitList = S.priceBook[key];
if (!hitList || !hitList.length) return null;
const bySpec = hitList.find(h => h.spec && normName(h.spec) === normName(spec));
return (bySpec || hitList[0]).price;

}

/ ---------- 单行派生量 ---------- /
function derive(r) {

const loss = 1 + (+r.loss || 0) / 100;
const q = +r.qty || 0;
const den = +r.density || 0;
const crown = Math.PI * Math.pow((+r.cw || 0) / 2, 2);

if (r.unit === "m²") {
  r._byArea = den <= 0;
  r._base = r._byArea ? q : q * den;            // 计价基数:面积 或 株数
  r._unitLabel = r._byArea ? "m²" : "株";
  r._canopy = q;                                 // 成片种植:覆盖面积即种植面积
} else {
  r._byArea = false;
  r._base = q;
  r._unitLabel = "株";
  r._canopy = q * crown;                         // 单株冠幅投影
}
r._final = r._base * loss;
r._sub = r._final * (+r.price || 0);
r._gv = r._canopy * (LEAF_DEPTH[r.type] || 0.5);
return r;

}

/ ---------- 加入配置表 ---------- /
function addRow(p) {

const spec = p.sp || "";
const booked = lookupPrice(p.n, spec);
S.plants.push({
  id: uid(),
  name: p.n, latin: p.l || "", type: p.t || "乔木", spec,
  cw: p.cw || 0, ev: p.ev ? 1 : 0, na: p.na ? 1 : 0, mo: p.mo || [],
  unit: (p.t === "乔木" || p.t === "小乔木") ? "株" : "m²",
  qty: 0,
  density: p.d === undefined ? 0 : p.d,
  loss: (p.t === "乔木" || p.t === "小乔木") ? 2 : 5,
  price: booked !== null ? booked : 0,     // 无价格清单时留空(0)
  priceSrc: booked !== null ? "book" : "",
  refPrice: p.p || 0                        // 仅作输入框灰字占位
});
renderTable();
recalcAll();

}

/ ---------- 植物库渲染 ---------- /
function renderLib() {

const kw = normName($("#libSearch").value);
const ft = $("#fType").value, fl = $("#fLight").value;
const fe = $("#fEver").value, fz = $("#fZone").value;
const fn = $("#fNative").checked, fs = $("#fSeason").checked;

const list = PLANT_DB.filter(p => {
  if (kw && !(normName(p.n).includes(kw) || normName(p.l).includes(kw))) return false;
  if (ft && p.t !== ft) return false;
  if (fl && p.li !== fl) return false;
  if (fe !== "" && String(p.ev ? 1 : 0) !== fe) return false;
  if (fz && !p.z.includes(fz)) return false;
  if (fn && !p.na) return false;
  if (fs && !(p.mo && p.mo.length)) return false;
  return true;
});

const box = $("#libList");
box.textContent = "";
list.forEach(p => {
  const el = document.createElement("div");
  el.className = "lib-item";
  el.title = "点击加入苗木配置表";

  const top = document.createElement("div");
  top.className = "li-top";
  const nm = document.createElement("span");
  nm.className = "li-name"; nm.textContent = p.n;
  const lt = document.createElement("span");
  lt.className = "li-latin"; lt.textContent = p.l;
  top.append(nm, lt);

  const tags = document.createElement("div");
  tags.className = "li-tags";
  const mk = (txt, cls) => { const s = document.createElement("span"); s.className = "tag " + (cls || ""); s.textContent = txt; return s; };
  tags.append(mk(p.t));
  tags.append(mk(p.ev ? "常绿" : "落叶", p.ev ? "ev" : "de"));
  tags.append(mk(p.li));
  if (p.na) tags.append(mk("乡土", "na"));
  if (p.se) tags.append(mk(p.se, "se"));
  tags.append(mk(p.sp));

  el.append(top, tags);
  el.onclick = () => { addRow(p); toast("已加入:" + p.n + "(请填单价)"); };
  box.appendChild(el);
});
$("#libCount").textContent = list.length;

}

/ ---------- 配置表渲染 ---------- /
const TYPES = ["乔木","小乔木","灌木","竹类","观赏草","地被","藤本","草坪","水生"];

function renderTable() {

const tb = $("#plantTable tbody");
tb.textContent = "";
S.plants.forEach(r => {
  derive(r);
  const tr = document.createElement("tr");

  tr.appendChild(cellInput(r, "name", "txt"));
  tr.appendChild(cellSelect(r, "type", TYPES));
  tr.appendChild(cellInput(r, "spec", "txt"));
  tr.appendChild(cellNum(r, "cw", 0.1));
  tr.appendChild(cellSelect(r, "unit", ["株", "m²"]));
  tr.appendChild(cellNum(r, "qty", 1));
  tr.appendChild(cellNum(r, "density", 0.5, r.unit !== "m²"));
  tr.appendChild(cellNum(r, "loss", 1));
  tr.appendChild(cellPrice(r));

  const c1 = document.createElement("td");
  c1.className = "calc-cell num";
  c1.textContent = fmt(r._final, r._unitLabel === "m²" ? 1 : 0) + " " + r._unitLabel;
  tr.appendChild(c1);

  const c2 = document.createElement("td");
  c2.className = "calc-cell num";
  c2.textContent = r.price > 0 ? fmt(r._sub, 0) : "—";
  tr.appendChild(c2);

  const c3 = document.createElement("td");
  const x = document.createElement("span");
  x.className = "del"; x.textContent = "✕"; x.title = "删除";
  x.onclick = () => { S.plants = S.plants.filter(p => p.id !== r.id); renderTable(); recalcAll(); };
  c3.appendChild(x);
  tr.appendChild(c3);

  tb.appendChild(tr);
});
$("#plantCount").textContent = S.plants.length;
updateFoot();

}

function cellInput(r, key, cls) {

const td = document.createElement("td");
const i = document.createElement("input");
i.type = "text"; i.className = cls || ""; i.value = r[key] || "";
i.oninput = () => { r[key] = i.value; };
i.onchange = () => { renderTable(); recalcAll(); };
td.appendChild(i);
return td;

}
function cellNum(r, key, step, disabled) {

const td = document.createElement("td");
const i = document.createElement("input");
i.type = "number"; i.step = step; i.min = 0; i.value = r[key];
i.disabled = !!disabled;
if (disabled) i.title = "仅「m²」计量时使用";
i.oninput = () => { r[key] = +i.value || 0; derive(r); updateFoot(); };
i.onchange = () => { renderTable(); recalcAll(); };
td.appendChild(i);
return td;

}
function cellSelect(r, key, opts) {

const td = document.createElement("td");
const s = document.createElement("select");
opts.forEach(o => s.add(new Option(o, o)));
s.value = r[key];
s.onchange = () => { r[key] = s.value; renderTable(); recalcAll(); };
td.appendChild(s);
return td;

}
/* 单价单元格:留空 + 灰字占位(内置估值)+ 来源着色 /
function cellPrice(r) {

const td = document.createElement("td");
const i = document.createElement("input");
i.type = "number"; i.step = 0.1; i.min = 0;
i.value = r.price > 0 ? r.price : "";
i.placeholder = r.refPrice ? "估≈" + r.refPrice : "待填";
i.className = r.priceSrc === "book" ? "from-book" : (r.price > 0 ? "" : "empty-price");
i.title = r.priceSrc === "book"
  ? "来自导入的价格清单"
  : (r.price > 0 ? "手工填写" : "尚未填写单价;灰字为无来源的经验估值,仅供量级参考");
i.oninput = () => {
  r.price = +i.value || 0;
  r.priceSrc = r.price > 0 ? "manual" : "";
  derive(r); updateFoot();
};
i.onchange = () => { renderTable(); recalcAll(); };
td.appendChild(i);
return td;

}

function updateFoot() {

const st = stats();
$("#cntTree").textContent = fmt0(st.tree) + " 株";
$("#cntShrub").textContent = fmt0(st.shrubQty) + " 株" + (st.shrubArea ? " / " + fmt(st.shrubArea, 0) + " m²" : "");
$("#cntGround").textContent = fmt(st.groundArea, 0) + " m²";
$("#cntCanopy").textContent = fmt(st.canopy, 0) + " m²";
$("#cntPlantCost").textContent = st.missing ? money(st.cost) + "(" + st.missing + " 项缺单价)" : money(st.cost);

}

/* 苗木汇总统计 /
function stats() {

const o = {tree:0, shrubQty:0, shrubArea:0, groundArea:0, canopy:0, cost:0, gv:0,
           ever:0, deci:0, native:0, total:0, missing:0, months:new Array(13).fill(0)};
S.plants.forEach(r => {
  derive(r);
  o.canopy += r._canopy;
  o.gv += r._gv;
  o.cost += r._sub;
  if (r.price <= 0 && r.qty > 0) o.missing++;

  const isTree = r.type === "乔木" || r.type === "小乔木";
  if (isTree) o.tree += r._final;
  else if (r.type === "灌木" || r.type === "竹类") {
    if (r._unitLabel === "株") o.shrubQty += r._final;
    if (r.unit === "m²") o.shrubArea += (+r.qty || 0);
  } else o.groundArea += (+r.qty || 0);

  const w = isTree ? (r._final || 1) : 1;   // 常绿落叶比按乔木株数加权
  if (isTree) { r.ev ? (o.ever += w) : (o.deci += w); }
  if (r.na) o.native++;
  o.total++;
  (r.mo || []).forEach(m => { if (m >= 1 && m <= 12) o.months[m]++; });
});
return o;

}

/ ---------- C:价格清单导入 / 模板 ---------- /
function parseCsv(text) {

const rows = [];
text = text.replace(/^\uFEFF/, "");
text.split(/\r?\n/).forEach(line => {
  if (!line.trim()) return;
  const cells = [];
  let cur = "", q = false;
  for (let i = 0; i < line.length; i++) {
    const ch = line[i];
    if (q) {
      if (ch === '"' && line[i + 1] === '"') { cur += '"'; i++; }
      else if (ch === '"') q = false;
      else cur += ch;
    } else if (ch === '"') q = true;
    else if (ch === "," || ch === "\t" || ch === ";") { cells.push(cur); cur = ""; }
    else cur += ch;
  }
  cells.push(cur);
  rows.push(cells.map(c => c.trim()));
});
return rows;

}

function importPriceCsv(text) {

const rows = parseCsv(text);
if (!rows.length) { toast("文件为空"); return; }
// 跳过表头
if (/品种|名称|苗木|name/i.test(rows[0][0] || "")) rows.shift();

const book = {};
let n = 0, bad = 0;
rows.forEach(c => {
  const name = c[0];
  const spec = c[1] || "";
  const price = parseFloat(String(c[2] || "").replace(/[^\d.]/g, ""));
  if (!name || !isFinite(price) || price <= 0) { bad++; return; }
  const k = normName(name);
  (book[k] = book[k] || []).push({price, spec, note:c[3] || "", raw:name});
  n++;
});
if (!n) { toast("未解析到有效记录,请检查列顺序:品种,规格,单价"); return; }

S.priceBook = Object.assign(S.priceBook, book);

// 套用到现有配置行
let hit = 0;
const miss = [];
S.plants.forEach(r => {
  const p = lookupPrice(r.name, r.spec);
  if (p !== null) { r.price = p; r.priceSrc = "book"; hit++; }
  else if (r.priceSrc !== "manual") miss.push(r.name);
});
renderTable(); syncPriceBookState(); recalcAll();

let msg = "价格清单已导入 " + n + " 条;配置表匹配 " + hit + " 行";
if (bad) msg += ";忽略无效行 " + bad;
if (miss.length) msg += ";未匹配:" + miss.slice(0, 4).join("、") + (miss.length > 4 ? " 等" + miss.length + "项" : "");
toast(msg);

}

function downloadTemplate() {

const lines = ["品种,规格,单价(元),备注(价格来源/询价日期)"];
const seen = new Set();
// 已在配置表中的品种优先,方便直接填价回传
S.plants.forEach(r => {
  const k = normName(r.name);
  if (seen.has(k)) return;
  seen.add(k);
  lines.push([csvCell(r.name), csvCell(r.spec), "", ""].join(","));
});
if (seen.size === 0) {
  ["香樟", "银杏", "红花檵木", "麦冬", "马尼拉草"].forEach(n => {
    const p = PLANT_DB.find(x => x.n === n);
    lines.push([csvCell(n), csvCell(p ? p.sp : ""), "", ""].join(","));
  });
}
Exporter.download("苗木价格清单模板.csv", "\uFEFF" + lines.join("\r\n"), "text/csv;charset=utf-8");
toast("模板已下载:填入当地信息价后再导入");

}
const csvCell = v => /[",\n]/.test(String(v)) ? '"' + String(v).replace(/"/g, '""') + '"' : String(v);

/* B 的显式逃生口:用户主动确认后才填入内置经验估值 /
function fillRefPrices() {

const targets = S.plants.filter(r => r.price <= 0 && r.refPrice > 0);
if (!targets.length) { toast("没有可填充的空单价行"); return; }
const ok = confirm(
  "即将把 " + targets.length + " 行空白单价填为内置经验估值。\n\n" +
  "这些数字没有定额或造价信息价来源,仅供量级参考,不可用于报价、投标、结算。\n\n确定继续?"
);
if (!ok) return;
targets.forEach(r => { r.price = r.refPrice; r.priceSrc = "ref"; });
renderTable(); recalcAll();
toast("已填入经验估值(" + targets.length + " 行),请务必替换为真实询价");

}

function syncPriceBookState() {

const n = Object.keys(S.priceBook || {}).length;
const el = $("#priceBookState");
el.textContent = n ? "已导入价格清单 " + n + " 个品种" : "当前未导入价格清单";
el.classList.toggle("on", n > 0);

}

/ ---------- 绑定 ---------- /
function bind() {

const types = Array.from(new Set(PLANT_DB.map(p => p.t)));
types.forEach(t => $("#fType").add(new Option(t, t)));
Array.from(new Set(PLANT_DB.map(p => p.li))).forEach(l => $("#fLight").add(new Option(l, l)));
Array.from(new Set(PLANT_DB.flatMap(p => p.z))).forEach(z => $("#fZone").add(new Option(z, z)));

["#libSearch", "#fType", "#fLight", "#fEver", "#fZone", "#fNative", "#fSeason"]
  .forEach(s => $(s).addEventListener("input", renderLib));

$("#btnAddCustom").onclick = () => {
  addRow({n:"自定义品种", l:"", t:"乔木", cw:3, ev:1, na:1, mo:[], sp:"", p:0, d:0});
  toast("已添加空行,请填写品种与单价");
};
$("#btnImportPrice").onclick = () => $("#filePrice").click();
$("#filePrice").onchange = e => {
  const f = e.target.files[0];
  if (!f) return;
  const fr = new FileReader();
  fr.onload = () => importPriceCsv(fr.result);
  fr.readAsText(f, "utf-8");
  e.target.value = "";
};
$("#btnPriceTpl").onclick = downloadTemplate;
$("#btnFillRef").onclick = fillRefPrices;

renderLib();
renderTable();
syncPriceBookState();

}

return {bind, renderLib, renderTable, stats, derive, syncPriceBookState, lookupPrice, normName};
})();