/* global React, FIELDS, SOURCES, INLINE_KEYS, CATEGORY_TREE, SYNTH_IMG_MARKER */
/* global CellDisplay, CellEditor, ImgMarkerCell, DeleteRowBtn */

// ─── static maps ────────────────────────────────────────────────────────────

const GROUP_LABELS = {
  A: "基本識別", B: "供應", C: "分類", D: "商務",
  E: "銷售設定", F: "規格與配送", G: "法規 / 合規",
  H: "圖片", I: "進貨資訊", J: "系統檢核",
};

// ─── column builder ──────────────────────────────────────────────────────────

function buildColumns(mode, formStatus, role) {
  if (mode === "compact") {
    return FIELDS.filter(f => INLINE_KEYS.includes(f.key));
  }

  const result = [];
  let hInserted = false;

  for (const f of FIELDS) {
    if (f.group === "H") {
      if (formStatus === "editing") {
        if (!hInserted) { result.push(SYNTH_IMG_MARKER); hInserted = true; }
        continue;
      }
    }
    result.push(f);
  }
  return result;
}

// ─── thead group builder ─────────────────────────────────────────────────────

function buildTheadGroups(cols) {
  const row1 = [];
  const row2 = [];
  const row3 = [];

  let i = 0;
  while (i < cols.length) {
    const group = cols[i].group;
    let j = i;
    while (j < cols.length && cols[j].group === group) j++;

    const groupCols = cols.slice(i, j);
    row1.push({ group, colSpan: groupCols.length, label: GROUP_LABELS[group] || group });

    const emittedSubBands = new Set();
    for (const col of groupCols) {
      if (col.subBand) {
        if (!emittedSubBands.has(col.subBand)) {
          emittedSubBands.add(col.subBand);
          const sbCount = groupCols.filter(c => c.subBand === col.subBand).length;
          row2.push({ type: "subBand", label: col.subBand, colSpan: sbCount });
        }
        row3.push({ col });
      } else {
        row2.push({ type: "field", col, colSpan: 1, rowSpan: 2 });
      }
    }
    i = j;
  }

  return { row1, row2, row3 };
}

// ─── helpers ─────────────────────────────────────────────────────────────────

function isApplicable(col, row) {
  if (!col.showWhen) return true;
  const { mainCat, subCat } = col.showWhen;
  if (mainCat && !mainCat.includes(row.mainCat)) return false;
  if (subCat && !subCat.includes(row.subCat)) return false;
  return true;
}

function isNonEditable(col) {
  return col.formula || col.readOnly || col.synthetic || col.type === "imgPlaceholder";
}

// ─── SkuTable ─────────────────────────────────────────────────────────────────

const SkuTable = ({
  skus, role, readOnly, errorMap = {}, mode, formStatus,
  onPatchCell, onDeleteRow, onAddRow, onToast,
  selectedRows = new Set(), onToggleRow, onToggleAll,
}) => {
  const [sel, setSel] = React.useState(null);
  // sel = { active: {r,c}, end: {r,c}, editing: boolean, draft: string|null }
  const [dragging, setDragging] = React.useState(false);
  const [fillDrag, setFillDrag] = React.useState(null);
  // fillDrag = { endR: number } — only tracks current target row during drag
  const wrapRef = React.useRef(null);
  const fillDragRef = React.useRef(null); // mirror fillDrag for document listener

  const allSelected = skus.length > 0 && skus.every(s => selectedRows.has(s.uid));
  const someSelected = skus.some(s => selectedRows.has(s.uid));

  const cols = React.useMemo(
    () => buildColumns(mode, formStatus, role),
    [mode, formStatus, role]
  );

  const bbox = React.useMemo(() => {
    if (!sel) return null;
    return {
      r0: Math.min(sel.active.r, sel.end.r),
      r1: Math.max(sel.active.r, sel.end.r),
      c0: Math.min(sel.active.c, sel.end.c),
      c1: Math.max(sel.active.c, sel.end.c),
    };
  }, [sel]);

  const canEdit = React.useCallback((r, c) => {
    if (readOnly) return false;
    const col = cols[c];
    if (!col) return false;
    if (isNonEditable(col)) return false;
    if (!isApplicable(col, skus[r])) return false;
    return true;
  }, [readOnly, cols, skus]);

  // ── selection helpers ──────────────────────────────────────────────────────

  const clampR = r => Math.max(0, Math.min(skus.length - 1, r));
  const clampC = c => Math.max(0, Math.min(cols.length - 1, c));

  const moveActive = React.useCallback((dr, dc) => {
    setSel(prev => {
      if (!prev) return null;
      const r = clampR(prev.active.r + dr);
      const c = clampC(prev.active.c + dc);
      return { active: { r, c }, end: { r, c }, editing: false, draft: null };
    });
  }, [skus.length, cols.length]);

  const extendEnd = React.useCallback((dr, dc) => {
    setSel(prev => {
      if (!prev) return null;
      const r = clampR(prev.end.r + dr);
      const c = clampC(prev.end.c + dc);
      return { ...prev, end: { r, c }, editing: false };
    });
  }, [skus.length, cols.length]);

  const startEditing = React.useCallback((draft = null) => {
    setSel(prev => {
      if (!prev || !canEdit(prev.active.r, prev.active.c)) return prev;
      return { ...prev, editing: true, draft };
    });
  }, [canEdit]);

  const stopEditing = React.useCallback(() => {
    setSel(prev => prev ? { ...prev, editing: false, draft: null } : null);
  }, []);

  // ── range ops ─────────────────────────────────────────────────────────────

  const commitEdit = (uid, key, value) => {
    if (value !== null && typeof value === "object" && !Array.isArray(value)) {
      Object.entries(value).forEach(([k, v]) => onPatchCell(uid, k, v));
    } else {
      onPatchCell(uid, key, value);
      // when a parent field changes, clear dependent child fields
      cols.forEach(col => { if (col.parentKey === key) onPatchCell(uid, col.key, ""); });
    }
  };

  const clearRange = () => {
    if (!bbox) return;
    for (let r = bbox.r0; r <= bbox.r1; r++) {
      for (let c = bbox.c0; c <= bbox.c1; c++) {
        if (!canEdit(r, c)) continue;
        const col = cols[c];
        let emptyVal;
        if (col.type === "tags" || col.type === "multiSelect")    emptyVal = [];
        else                                                       emptyVal = "";
        commitEdit(skus[r].uid, col.key, emptyVal);
      }
    }
  };

  const copyRange = () => {
    if (!bbox) return;
    const rows = [];
    for (let r = bbox.r0; r <= bbox.r1; r++) {
      const cells = [];
      for (let c = bbox.c0; c <= bbox.c1; c++) {
        cells.push(String(skus[r][cols[c].key] ?? ""));
      }
      rows.push(cells.join("\t"));
    }
    navigator.clipboard.writeText(rows.join("\n"))
      .then(() => { if (onToast) onToast(`已複製 ${bbox.r1 - bbox.r0 + 1}×${bbox.c1 - bbox.c0 + 1} 格`); })
      .catch(() => {});
  };

  const pasteAtActive = () => {
    if (!sel || readOnly) return;
    navigator.clipboard.readText().then(text => {
      const lines = text.trim().split("\n").map(l => l.split("\t"));
      let truncated = false;
      const { r: startR, c: startC } = sel.active;
      let pasteR1 = startR, pasteC1 = startC;
      for (let dr = 0; dr < lines.length; dr++) {
        const r = startR + dr;
        if (r >= skus.length) { truncated = true; break; }
        for (let dc = 0; dc < lines[dr].length; dc++) {
          const c = startC + dc;
          if (c >= cols.length) { truncated = true; continue; }
          if (canEdit(r, c)) {
            commitEdit(skus[r].uid, cols[c].key, lines[dr][dc]);
            pasteR1 = Math.max(pasteR1, r);
            pasteC1 = Math.max(pasteC1, c);
          }
        }
      }
      setSel({ active: sel.active, end: { r: pasteR1, c: pasteC1 }, editing: false, draft: null });
      if (truncated && onToast) onToast("貼上範圍超出表格，已截斷", "warn");
    }).catch(() => {});
  };

  // ── fill handle drag ──────────────────────────────────────────────────────

  React.useEffect(() => { fillDragRef.current = fillDrag; }, [fillDrag]);

  React.useEffect(() => {
    const onMove = (e) => {
      if (!fillDragRef.current) return;
      const el = document.elementFromPoint(e.clientX, e.clientY);
      if (!el) return;
      const td = el.closest("[data-r]");
      if (!td) return;
      const r = parseInt(td.dataset.r);
      setFillDrag(prev => prev ? { ...prev, endR: r } : null);
    };
    const onUp = () => {
      const fd = fillDragRef.current;
      if (fd && bbox && fd.endR > bbox.r1) {
        const srcCount = bbox.r1 - bbox.r0 + 1;
        for (let dr = 0; dr < fd.endR - bbox.r1; dr++) {
          const r = bbox.r1 + 1 + dr;
          if (r >= skus.length) break;
          for (let c = bbox.c0; c <= bbox.c1; c++) {
            if (!canEdit(r, c)) continue;
            const col = cols[c];
            const srcR = bbox.r0 + (dr % srcCount);
            commitEdit(skus[r].uid, col.key, skus[srcR][col.key]);
          }
        }
        setSel(prev => prev ? { ...prev, end: { r: fd.endR, c: bbox.c1 } } : prev);
      }
      setFillDrag(null);
    };
    document.addEventListener("mousemove", onMove);
    document.addEventListener("mouseup", onUp);
    return () => {
      document.removeEventListener("mousemove", onMove);
      document.removeEventListener("mouseup", onUp);
    };
  }, [bbox, canEdit, skus, cols]);

  // ── drag selection (mouseup cleanup) ─────────────────────────────────────

  React.useEffect(() => {
    const onUp = () => setDragging(false);
    document.addEventListener("mouseup", onUp);
    return () => document.removeEventListener("mouseup", onUp);
  }, []);

  // ── keyboard handler ──────────────────────────────────────────────────────

  const handleKeyDown = (e) => {
    if (sel && sel.editing) return;
    if (!sel) return;

    if (e.ctrlKey || e.metaKey) {
      switch (e.key.toLowerCase()) {
        case "a": e.preventDefault(); setSel({ active: {r:0,c:0}, end: {r:skus.length-1,c:cols.length-1}, editing:false, draft:null }); return;
        case "c": e.preventDefault(); copyRange(); return;
        case "v": e.preventDefault(); pasteAtActive(); return;
      }
      return;
    }

    switch (e.key) {
      case "ArrowUp":    e.preventDefault(); e.shiftKey ? extendEnd(-1,0) : moveActive(-1,0); break;
      case "ArrowDown":  e.preventDefault(); e.shiftKey ? extendEnd(1,0)  : moveActive(1,0);  break;
      case "ArrowLeft":  e.preventDefault(); e.shiftKey ? extendEnd(0,-1) : moveActive(0,-1); break;
      case "ArrowRight": e.preventDefault(); e.shiftKey ? extendEnd(0,1)  : moveActive(0,1);  break;
      case "Tab":        e.preventDefault(); e.shiftKey ? moveActive(0,-1) : moveActive(0,1); break;
      case "Enter":      e.preventDefault(); startEditing(); break;
      case "F2":         e.preventDefault(); startEditing(); break;
      case "Escape":
        e.preventDefault();
        setSel(prev => prev ? { ...prev, end: {...prev.active}, editing: false, draft: null } : null);
        break;
      case "Delete":
      case "Backspace":
        e.preventDefault();
        clearRange();
        break;
      default:
        if (e.key.length === 1 && !e.altKey) {
          if (sel && canEdit(sel.active.r, sel.active.c)) startEditing(e.key);
        }
    }
  };

  // ── mouse handlers ────────────────────────────────────────────────────────

  const getCellCoords = (target) => {
    const td = target.closest("[data-r]");
    if (!td) return null;
    return { r: parseInt(td.dataset.r), c: parseInt(td.dataset.c) };
  };

  const handleMouseDown = (e) => {
    if (e.button !== 0) return;
    const tag = e.target.tagName;
    if (tag === "SELECT" || tag === "INPUT" || tag === "TEXTAREA" || tag === "BUTTON") return;
    const coords = getCellCoords(e.target);
    if (!coords) return;
    if (e.shiftKey && sel) {
      setSel(prev => ({ ...prev, end: coords, editing: false }));
    } else {
      const editing = canEdit(coords.r, coords.c);
      setSel({ active: coords, end: coords, editing, draft: null });
      setDragging(!editing);
    }
    if (wrapRef.current) wrapRef.current.focus();
    e.preventDefault();
  };

  const handleMouseMove = (e) => {
    if (!dragging) return;
    const coords = getCellCoords(e.target);
    if (!coords) return;
    setSel(prev => prev ? { ...prev, end: coords } : prev);
  };

  const handleDblClick = (e) => {
    const coords = getCellCoords(e.target);
    if (!coords || !canEdit(coords.r, coords.c)) return;
    setSel(prev => prev ? { ...prev, editing: true } : prev);
  };

  // ── thead ─────────────────────────────────────────────────────────────────

  const { row1, row2, row3 } = buildTheadGroups(cols);
  const hasRow3 = row3.length > 0;
  const theadRows = hasRow3 ? 3 : 2;

  // ── render ────────────────────────────────────────────────────────────────

  return (
    <div
      className={`sku-tbl-wrap${fillDrag ? " sku-tbl-wrap--fill-dragging" : ""}`}
      ref={wrapRef}
      tabIndex={-1}
      onKeyDown={handleKeyDown}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onDoubleClick={handleDblClick}
    >
      <table className="sku-tbl">
        <thead>
          {/* ── Row 1: group bands ──────────────────────────────────────── */}
          <tr className="sku-tbl__thead-r1">
            {!readOnly && (
              <th className="sku-tbl__th-ctrl stk-l-0" rowSpan={theadRows} style={{width:36}}>
                {onToggleAll && (
                  <input type="checkbox"
                    checked={allSelected}
                    ref={el => { if (el) el.indeterminate = someSelected && !allSelected; }}
                    onChange={e => onToggleAll(e.target.checked)}
                    onClick={e => e.stopPropagation()}
                    onMouseDown={e => e.stopPropagation()}
                  />
                )}
              </th>
            )}
            <th className={`sku-tbl__th-ctrl ${readOnly ? "stk-l-0" : "stk-l-36"}`} rowSpan={theadRows} style={{width:36}}>#</th>
            {row1.map((g, i) => (
              <th key={i} colSpan={g.colSpan}
                className={`sku-tbl__th-group th-${['a','b','c','d','e','f','g','h'][i % 8]}`}>
                {g.label}
              </th>
            ))}
            {!readOnly && (
              <th className="sku-tbl__th-ctrl" rowSpan={theadRows} style={{width:44}}>操作</th>
            )}
          </tr>
          {/* ── Row 2: sub-band or field (rowSpan=2 if no subBand) ──────── */}
          <tr className="sku-tbl__thead-r2">
            {row2.map((cell, i) => {
              if (cell.type === "subBand") {
                return (
                  <th key={i} colSpan={cell.colSpan} className="sku-tbl__th-subband">
                    {cell.label}
                  </th>
                );
              }
              const barcodeThCls = cell.col.key === "barcode"
                ? (readOnly ? " stk-l-36 stk-shadow" : " stk-l-72 stk-shadow") : "";
              return (
                <th key={i} colSpan={1} rowSpan={cell.rowSpan}
                  className={`sku-tbl__th-field${cell.col.required ? " sku-tbl__th-req" : ""}${barcodeThCls}`}
                  style={{ minWidth: cell.col.w }}>
                  {cell.col.label}{cell.col.required && <span className="req-star"> *</span>}
                </th>
              );
            })}
          </tr>
          {/* ── Row 3: sub-band children ────────────────────────────────── */}
          {hasRow3 && (
            <tr className="sku-tbl__thead-r3">
              {row3.map((cell, i) => (
                <th key={i}
                  className={`sku-tbl__th-field${cell.col.required ? " sku-tbl__th-req" : ""}`}
                  style={{ minWidth: cell.col.w }}>
                  {cell.col.label}{cell.col.required && <span className="req-star"> *</span>}
                </th>
              ))}
            </tr>
          )}
        </thead>
        <tbody>
          {skus.map((row, ri) => (
            <tr key={row.uid}
              className={`sku-tbl__row${sel && (ri >= (bbox ? bbox.r0 : -1) && ri <= (bbox ? bbox.r1 : -1)) ? " sku-tbl__row--in-range" : ""}`}>
              {/* checkbox */}
              {!readOnly && (
                <td className="sku-tbl__td-ctrl stk-l-0">
                  {onToggleRow && (
                    <input type="checkbox"
                      checked={selectedRows.has(row.uid)}
                      onChange={() => onToggleRow(row.uid)}
                      onClick={e => e.stopPropagation()}
                      onMouseDown={e => e.stopPropagation()}
                    />
                  )}
                </td>
              )}
              {/* row number */}
              <td className={`sku-tbl__td-ctrl ${readOnly ? "stk-l-0" : "stk-l-36"}`}>{ri + 1}</td>
              {/* data cells */}
              {cols.map((col, ci) => {
                const na = !col.synthetic && col.type !== "imgPlaceholder" && !isApplicable(col, row);
                const errMsg = (errorMap[row.uid] || {})[col.key];
                const isActive = sel && sel.active.r === ri && sel.active.c === ci;
                const inRange = bbox
                  ? (ri >= bbox.r0 && ri <= bbox.r1 && ci >= bbox.c0 && ci <= bbox.c1)
                  : false;
                const isEditing = isActive && sel && sel.editing;
                const isBottomRight = bbox && ri === bbox.r1 && ci === bbox.c1;
                const ro = !readOnly && isNonEditable(col);

                let cls = "sku-tbl__cell";
                if (col.key === "barcode") cls += readOnly ? " stk-l-36 stk-shadow" : " stk-l-72 stk-shadow";
                if (na)        cls += " sku-tbl__cell--na";
                if (ro)        cls += " sku-tbl__cell--ro";
                if (errMsg)    cls += " sku-tbl__cell--err";
                if (inRange)   cls += " in-range";
                if (isActive)  cls += " is-active";
                if (isEditing) cls += " is-editing";

                return (
                  <td
                    key={col.key}
                    data-r={ri}
                    data-c={ci}
                    className={cls}
                    style={{ width: col.w, minWidth: col.w }}
                  >
                    {/* synthetic image marker */}
                    {col.synthetic && <ImgMarkerCell row={row} single={true} />}

                    {/* individual image col (post-submit) */}
                    {col.type === "imgPlaceholder" && <ImgMarkerCell row={row} single={col.key} />}

                    {/* N/A cell */}
                    {na && <span className="cell-na">不適用</span>}

                    {/* editing */}
                    {!col.synthetic && col.type !== "imgPlaceholder" && !na && isEditing && (
                      <CellEditor
                        field={col}
                        value={sel.draft !== null ? sel.draft : row[col.key]}
                        row={row}
                        onCommit={(val, dir) => {
                          commitEdit(row.uid, col.key, val);
                          if      (dir === "down")  moveActive(1, 0);
                          else if (dir === "up")    moveActive(-1, 0);
                          else if (dir === "right") moveActive(0, 1);
                          else if (dir === "left")  moveActive(0, -1);
                          else if (dir === "blur")  { /* value saved; navigation handled by the click that caused blur */ }
                          else                      stopEditing();
                        }}
                        onCancel={stopEditing}
                      />
                    )}

                    {/* display (non-editing, non-synthetic, non-na) */}
                    {!col.synthetic && col.type !== "imgPlaceholder" && !na && !isEditing && (
                      <CellDisplay
                        field={col}
                        value={row[col.key]}
                        row={row}
                        errMsg={errMsg}
                        onDirectPatch={(!readOnly && col.type === "radio") ? (val) => onPatchCell(row.uid, col.key, val) : undefined}
                      />
                    )}

                    {/* error message */}
                    {errMsg && !isEditing && (
                      <div className="cell-err-msg">{errMsg}</div>
                    )}

                    {/* fill handle */}
                    {isBottomRight && !readOnly && canEdit(ri, ci) && sel && !sel.editing && (
                      <div
                        className="sku-tbl__fill-handle"
                        onMouseDown={e => {
                          e.stopPropagation();
                          e.preventDefault();
                          setFillDrag({ endR: bbox.r1 });
                        }}
                      />
                    )}
                  </td>
                );
              })}
              {/* delete */}
              {!readOnly && (
                <td className="sku-tbl__td-ctrl" onMouseDown={e => e.stopPropagation()}>
                  <DeleteRowBtn onClick={() => onDeleteRow(row.uid)} />
                </td>
              )}
            </tr>
          ))}
        </tbody>
      </table>

      {/* add row button */}
      {!readOnly && onAddRow && (
        <button className="tbl-add-row" onClick={onAddRow}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
            stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
          </svg>
          新增品項
        </button>
      )}
    </div>
  );
};

Object.assign(window, { SkuTable });
