/ ===== 图纸量算:比例尺标定 + 多边形测面积 + 折线测长度 ===== /
window.Measure = (function () {
const wrap = () => $("#canvasWrap");
let cv, ctx, img = null;
let view = {s:1, ox:0, oy:0};
let mode = "pan"; // pan | poly | line | calib
let draft = []; // 正在绘制的点(图像坐标)
let cursor = null; // 当前鼠标图像坐标(用于橡皮筋)
let hoverId = null, selId = null;
let drag = null; // {x,y,ox,oy,moved}

/ ---------- 坐标换算 ---------- /
const toScreen = p => ({x:p.x view.s + view.ox, y:p.y view.s + view.oy});
const toImage = (x, y) => ({x:(x - view.ox) / view.s, y:(y - view.oy) / view.s});

function evPos(e) {

const r = cv.getBoundingClientRect();
return {x:e.clientX - r.left, y:e.clientY - r.top};

}

/ ---------- 几何 ---------- /
function polyAreaPx(pts) {

let a = 0;
for (let i = 0, n = pts.length; i < n; i++) {
  const p = pts[i], q = pts[(i + 1) % n];
  a += p.x * q.y - q.x * p.y;
}
return Math.abs(a) / 2;

}
function pathLenPx(pts) {

let L = 0;
for (let i = 1; i < pts.length; i++) L += Math.hypot(pts[i].x - pts[i-1].x, pts[i].y - pts[i-1].y);
return L;

}
function centroid(pts) {

let x = 0, y = 0;
pts.forEach(p => { x += p.x; y += p.y; });
return {x:x / pts.length, y:y / pts.length};

}

/* 按当前比例尺重算所有图元的实测值 /
function recomputeShapes() {

const k = S.scale.pxPerM;
S.shapes.forEach(sh => {
  if (sh.kind === "poly") {
    sh.area = k > 0 ? polyAreaPx(sh.pts) / (k * k) : 0;
    sh.len  = k > 0 ? (pathLenPx(sh.pts) + Math.hypot(sh.pts[0].x - sh.pts[sh.pts.length-1].x, sh.pts[0].y - sh.pts[sh.pts.length-1].y)) / k : 0;
  } else {
    sh.area = 0;
    sh.len  = k > 0 ? pathLenPx(sh.pts) / k : 0;
  }
});

}

/ ---------- 画布尺寸 ---------- /
function resize() {

const dpr = window.devicePixelRatio || 1;
const r = wrap().getBoundingClientRect();
cv.width = Math.max(1, Math.round(r.width * dpr));
cv.height = Math.max(1, Math.round(r.height * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
draw();

}

function fit() {

if (!img) return;
const r = wrap().getBoundingClientRect();
const s = Math.min(r.width / img.width, r.height / img.height) * 0.96;
view.s = s;
view.ox = (r.width - img.width * s) / 2;
view.oy = (r.height - img.height * s) / 2;
draw();

}

/ ---------- 绘制 ---------- /
function draw() {

const r = wrap().getBoundingClientRect();
ctx.clearRect(0, 0, r.width, r.height);
if (img) {
  ctx.imageSmoothingQuality = "high";
  ctx.drawImage(img, view.ox, view.oy, img.width * view.s, img.height * view.s);
}

S.shapes.forEach((sh, i) => drawShape(sh, i + 1));

// 比例尺标定线
if (S.scale.line && S.scale.line.length === 2) {
  const a = toScreen(S.scale.line[0]), b = toScreen(S.scale.line[1]);
  ctx.save();
  ctx.strokeStyle = "#c0392b"; ctx.lineWidth = 2; ctx.setLineDash([6, 4]);
  ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
  ctx.setLineDash([]);
  [a, b].forEach(p => { ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, 7); ctx.fillStyle = "#c0392b"; ctx.fill(); });
  label((a.x + b.x) / 2, (a.y + b.y) / 2 - 10, "基准 " + fmt(S.scale.refLen, 2) + "m", "#c0392b");
  ctx.restore();
}

drawDraft();

}

function drawShape(sh, no) {

if (!sh.pts.length) return;
const col = sh.kind === "line" ? "#c98a1b" : catByKey(sh.cat).color;
const on = sh.id === hoverId || sh.id === selId;
ctx.save();
ctx.beginPath();
sh.pts.forEach((p, i) => { const q = toScreen(p); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
if (sh.kind === "poly") {
  ctx.closePath();
  ctx.fillStyle = hex2rgba(col, on ? 0.45 : 0.28);
  ctx.fill();
}
ctx.strokeStyle = col;
ctx.lineWidth = on ? 3 : 1.8;
ctx.stroke();

const c = sh.kind === "poly" ? centroid(sh.pts) : sh.pts[sh.pts.length - 1];
const sc = toScreen(c);
const txt = sh.kind === "poly"
  ? no + "· " + fmt(sh.area, 1) + "m²"
  : no + "· " + fmt(sh.len, 1) + "m";
label(sc.x, sc.y, txt, col);
ctx.restore();

}

function drawDraft() {

if (!draft.length) return;
const col = mode === "calib" ? "#c0392b" : (mode === "line" ? "#c98a1b" : catByKey($("#measureCat").value).color);
ctx.save();
ctx.beginPath();
draft.forEach((p, i) => { const q = toScreen(p); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
if (cursor) { const q = toScreen(cursor); ctx.lineTo(q.x, q.y); }
ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.setLineDash([5, 3]); ctx.stroke();
ctx.setLineDash([]);

if (mode === "poly" && draft.length >= 2 && cursor) {
  const pts = draft.concat([cursor]);
  ctx.beginPath();
  pts.forEach((p, i) => { const q = toScreen(p); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
  ctx.closePath();
  ctx.fillStyle = hex2rgba(col, 0.18); ctx.fill();
  const k = S.scale.pxPerM;
  if (k > 0) {
    const c = toScreen(centroid(pts));
    label(c.x, c.y, fmt(polyAreaPx(pts) / (k * k), 1) + "m²", col);
  }
}
if (mode !== "poly" && draft.length >= 1 && cursor && S.scale.pxPerM > 0) {
  const L = pathLenPx(draft.concat([cursor])) / S.scale.pxPerM;
  const q = toScreen(cursor);
  label(q.x, q.y - 14, fmt(L, 2) + "m", col);
}
draft.forEach(p => {
  const q = toScreen(p);
  ctx.beginPath(); ctx.arc(q.x, q.y, 3.5, 0, 7);
  ctx.fillStyle = "#fff"; ctx.fill(); ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.stroke();
});
ctx.restore();

}

function label(x, y, text, color) {

ctx.save();
ctx.font = "600 11px -apple-system,PingFang SC,sans-serif";
ctx.textAlign = "center"; ctx.textBaseline = "middle";
const w = ctx.measureText(text).width + 10;
ctx.fillStyle = "rgba(255,255,255,.88)";
roundRect(x - w / 2, y - 8, w, 16, 4); ctx.fill();
ctx.strokeStyle = hex2rgba(color, .5); ctx.lineWidth = 1;
roundRect(x - w / 2, y - 8, w, 16, 4); ctx.stroke();
ctx.fillStyle = "#1c2521"; ctx.fillText(text, x, y);
ctx.restore();

}
function roundRect(x, y, w, h, r) {

ctx.beginPath();
ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r); ctx.closePath();

}
function hex2rgba(h, a) {

const n = parseInt(h.slice(1), 16);
return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;

}

/ ---------- 模式 ---------- /
function setMode(m) {

mode = m; draft = []; cursor = null;
$$("#tab-measure .btn").forEach(b => b.classList.remove("active-mode"));
const map = {pan:"#btnPan", poly:"#btnPoly", line:"#btnLine", calib:"#btnCalib"};
if (map[m]) $(map[m]).classList.add("active-mode");
wrap().classList.toggle("mode-pan", m === "pan");
const tips = {
  pan:"拖动平移,滚轮缩放",
  poly:"沿边界连续点击 → 双击 / 回车闭合",
  line:"连续点击 → 双击 / 回车结束",
  calib:"点击已知长度的两个端点"
};
$("#hudTip").textContent = tips[m] || "";
draw();

}

/ ---------- 完成绘制 ---------- /
function finish() {

if (mode === "calib") {
  if (draft.length < 2) return;
  const px = Math.hypot(draft[1].x - draft[0].x, draft[1].y - draft[0].y);
  const real = +$("#calibLen").value;
  if (!(px > 0 && real > 0)) { toast("两点重合或实际长度无效"); draft = []; draw(); return; }
  S.scale.pxPerM = px / real;
  S.scale.calibrated = true;
  S.scale.refLen = real;
  S.scale.line = draft.slice(0, 2);
  draft = [];
  recomputeShapes(); syncScaleHud(); renderList(); setMode("poly");
  toast("比例尺已标定:1m ≈ " + fmt(S.scale.pxPerM, 2) + "px");
  return;
}
const need = mode === "poly" ? 3 : 2;
if (draft.length < need) { toast("至少需要 " + need + " 个点"); return; }
const cat = mode === "poly" ? $("#measureCat").value : "road";
S.shapes.push({id:uid(), kind:mode, cat, pts:draft.slice(), area:0, len:0});
draft = [];
recomputeShapes(); renderList(); draw();

}

/ ---------- 图元列表 ---------- /
function renderList() {

const box = $("#shapeList");
box.innerHTML = "";
S.shapes.forEach((sh, i) => {
  const el = document.createElement("div");
  el.className = "shape-item" + (sh.id === selId ? " sel" : "");
  const col = sh.kind === "line" ? "#c98a1b" : catByKey(sh.cat).color;
  const val = sh.kind === "poly" ? fmt(sh.area, 1) + " m²" : fmt(sh.len, 1) + " m";
  el.innerHTML =
    `<span class="sw" style="background:${col}"></span>` +
    `<span class="nm">${i + 1}. ${sh.kind === "poly" ? catByKey(sh.cat).n : "长度测量"}</span>` +
    `<span class="vl">${val}</span><span class="x" title="删除">✕</span>`;
  el.onmouseenter = () => { hoverId = sh.id; draw(); };
  el.onmouseleave = () => { hoverId = null; draw(); };
  el.onclick = e => {
    if (e.target.classList.contains("x")) {
      S.shapes = S.shapes.filter(s => s.id !== sh.id);
      renderList(); draw(); return;
    }
    selId = selId === sh.id ? null : sh.id;
    renderList(); draw();
  };
  box.appendChild(el);
});
$("#shapeCount").textContent = S.shapes.length;
const sa = S.shapes.filter(s => s.kind === "poly").reduce((a, s) => a + s.area, 0);
const sl = S.shapes.filter(s => s.kind === "line").reduce((a, s) => a + s.len, 0);
$("#sumArea").textContent = fmt(sa, 1) + " m²";
$("#sumLen").textContent = fmt(sl, 1) + " m";

}

function syncScaleHud() {

const ok = S.scale.pxPerM > 0;
$("#hudScale").textContent = ok ? "比例尺:1 m = " + fmt(S.scale.pxPerM, 2) + " px" : "比例尺:未标定";
$("#calibState").textContent = ok
  ? "状态:已标定(1 px = " + fmt(1 / S.scale.pxPerM, 4) + " m)"
  : "状态:未标定";

}

/ ---------- 图片载入 ---------- /
function loadImageSrc(src) {

const im = new Image();
im.onload = () => {
  img = im;
  S.image = {dataUrl:src, w:im.width, h:im.height};
  $("#dropHint").classList.add("hide");
  fit();
};
im.onerror = () => toast("图片加载失败");
im.src = src;

}
function loadFile(f) {

if (!f || !/^image\//.test(f.type)) { toast("请选择图片文件"); return; }
const fr = new FileReader();
fr.onload = () => loadImageSrc(fr.result);
fr.readAsDataURL(f);

}

/ ---------- 事件 ---------- /
function bind() {

cv = $("#board"); ctx = cv.getContext("2d");

const sel = $("#measureCat");
LAND_CATS.forEach(c => sel.add(new Option(c.n, c.k)));

new ResizeObserver(resize).observe(wrap());
resize();

cv.addEventListener("mousedown", e => {
  if (e.button !== 0) return;
  const p = evPos(e);
  drag = {x:p.x, y:p.y, ox:view.ox, oy:view.oy, moved:0};
});
cv.addEventListener("mousemove", e => {
  const p = evPos(e);
  cursor = toImage(p.x, p.y);
  if (drag) {
    drag.moved = Math.max(drag.moved, Math.hypot(p.x - drag.x, p.y - drag.y));
    if (mode === "pan" || drag.moved > 5) {
      view.ox = drag.ox + (p.x - drag.x);
      view.oy = drag.oy + (p.y - drag.y);
    }
  }
  if (mode !== "pan" || drag) draw();
});
cv.addEventListener("mouseup", e => {
  const wasClick = drag && drag.moved < 5;
  drag = null;
  if (!wasClick || mode === "pan") return;
  if (!img) { toast("请先载入平面图"); return; }
  if (mode !== "calib" && S.scale.pxPerM <= 0) { toast("请先标定比例尺"); return; }
  const p = evPos(e);
  draft.push(toImage(p.x, p.y));
  if (mode === "calib" && draft.length === 2) finish();
  draw();
});
cv.addEventListener("mouseleave", () => { cursor = null; draw(); });
cv.addEventListener("dblclick", () => {
  if (mode === "pan" || draft.length < 2) return;
  const a = draft[draft.length - 1], b = draft[draft.length - 2];
  if (Math.hypot(a.x - b.x, a.y - b.y) < 3 / view.s) draft.pop();
  finish(); draw();
});
cv.addEventListener("wheel", e => {
  e.preventDefault();
  const p = evPos(e);
  const before = toImage(p.x, p.y);
  const f = e.deltaY < 0 ? 1.12 : 1 / 1.12;
  view.s = Math.min(40, Math.max(0.02, view.s * f));
  view.ox = p.x - before.x * view.s;
  view.oy = p.y - before.y * view.s;
  draw();
}, {passive:false});
cv.addEventListener("contextmenu", e => { e.preventDefault(); if (draft.length) { draft.pop(); draw(); } });

document.addEventListener("keydown", e => {
  if (/INPUT|SELECT|TEXTAREA/.test(document.activeElement.tagName)) return;
  if (e.key === "Enter") { finish(); draw(); }
  else if (e.key === "Backspace") { if (draft.length) { draft.pop(); draw(); e.preventDefault(); } }
  else if (e.key === "Escape") { draft = []; draw(); }
  else if (e.key === "1") setMode("poly");
  else if (e.key === "2") setMode("line");
  else if (e.key === "3") setMode("pan");
});

// 拖拽 / 选择图片
const w = wrap();
["dragenter", "dragover"].forEach(t => w.addEventListener(t, e => { e.preventDefault(); w.classList.add("dragover"); }));
["dragleave", "drop"].forEach(t => w.addEventListener(t, e => { e.preventDefault(); w.classList.remove("dragover"); }));
w.addEventListener("drop", e => loadFile(e.dataTransfer.files[0]));
$("#fileImg").onchange = e => loadFile(e.target.files[0]);

$("#btnCalib").onclick = () => setMode("calib");
$("#btnPoly").onclick = () => setMode("poly");
$("#btnLine").onclick = () => setMode("line");
$("#btnPan").onclick = () => setMode("pan");
$("#btnFit").onclick = fit;
$("#btnClearAll").onclick = () => {
  if (!S.shapes.length || !confirm("确定清空全部量算图元?")) return;
  S.shapes = []; selId = null; renderList(); draw();
};
$("#btnScaleByDpi").onclick = () => {
  const sc = +$("#drawScale").value, dpi = +$("#drawDpi").value;
  if (!(sc > 0 && dpi > 0)) { toast("参数无效"); return; }
  // 图上 1m = (1000/sc) mm = (1000/sc)/25.4 inch → × dpi 得像素
  S.scale.pxPerM = (1000 / sc) / 25.4 * dpi;
  S.scale.calibrated = true; S.scale.refLen = 0; S.scale.line = null;
  recomputeShapes(); syncScaleHud(); renderList(); draw();
  toast("已按比例 1:" + sc + " / " + dpi + "dpi 换算");
};
$("#btnPushLand").onclick = pushToLand;

setMode("pan");
syncScaleHud();
renderList();

}

/* 量算结果汇总到用地平衡表 /
function pushToLand() {

const polys = S.shapes.filter(s => s.kind === "poly");
if (!polys.length) { toast("尚无面积图元"); return; }
const agg = {};
polys.forEach(s => { agg[s.cat] = (agg[s.cat] || 0) + s.area; });
Object.keys(agg).forEach(k => S.land[k] = +agg[k].toFixed(2));
renderLandTable();
recalcAll();
switchTab("land");
toast("已汇总 " + Object.keys(agg).length + " 类用地面积");

}

function restore() {

if (S.image && S.image.dataUrl) loadImageSrc(S.image.dataUrl);
else { img = null; $("#dropHint").classList.remove("hide"); draw(); }
recomputeShapes(); syncScaleHud(); renderList();

}

function snapshot() { return cv.toDataURL("image/png"); }

return {bind, restore, draw, fit, snapshot, renderList, recomputeShapes, syncScaleHud};
})();