/* ============================================================================= * spreadsheet_link_set.ts (주인 D) * 알약 키를 칸에 잇기 · 풀기 — 밖(M02 칸 지정 모달)에서 부르는 길(손잡이 `setLink`). * 알약 누름(`spreadsheet_links.ts` `link`)과 같은 규칙: 키 하나 = 칸 하나 · 칸 하나 = 키 하나 · * 다른 시트에 있던 같은 키 연결은 빠짐. 바꿈은 `시트통째`(되돌리기 짝) · DOM 을 모름(시험이 Node 로 부름). * ========================================================================== */ import { parseA1, toA1 } from "./spreadsheet_address"; import type { CellLink, Command, LinkMaterial, Workbook } from "./spreadsheet_types"; /** 연결에 같이 적을 알약 정보 — 없으면 있던 연결 것을 그대로 */ export interface LinkInfo { 이름?: string; 단위?: string; 자재?: LinkMaterial; } /** `칸`(A1 · null = 풀기)을 `sheetId` 시트에 이을 명령 — 바뀔 것 없으면 null · 칸 주소가 틀리면 false */ export function linkCommand( book: Workbook, sheetId: string, key: string, cell: string | null, info: LinkInfo = {}, ): Command | null | false { const at = cell === null ? null : parseA1(cell.trim().toUpperCase()); if (cell !== null && !at) return false; const a1 = at ? toA1(at.r, at.c) : null; const old = book.시트.flatMap((s) => s.연결 ?? []).find((l) => l.키 === key); const home = book.시트.find((s) => s.id === sheetId); if (a1 && !home) return false; if (a1 && home?.연결?.some((l) => l.키 === key && l.칸 === a1)) return null; // 이미 그 칸 const one: CellLink | null = a1 ? { 키: key, 칸: a1 } : null; if (one) { const 이름 = info.이름 ?? old?.이름; const 단위 = info.단위 ?? old?.단위; const 자재 = info.자재 ?? old?.자재; if (이름) one.이름 = 이름; if (단위) one.단위 = 단위; if (자재) one.자재 = 자재; } const commands: Command[] = []; for (const s of book.시트) { const was = s.연결 ?? []; const rest = was.filter((l) => l.키 !== key && !(s === home && a1 && l.칸 === a1)); const next = s === home && one ? [...rest, one] : rest; if (next.length === was.length && next.every((l, i) => l === was[i])) continue; const copy = structuredClone(s); if (next.length) copy.연결 = next; else delete copy.연결; commands.push({ 종류: "시트통째", 시트: copy }); } if (!commands.length) return null; return commands.length === 1 ? commands[0] : { 종류: "묶음", 명령: commands }; }