/* ============================================================================= * M02_SO_Def_Reflect.ts * 구조물 표 편집(PLAN 322-2)의 순수 손질 — 정의 사본의 `반영` · `부품` · 목록 + enabled · 형상칸. * * · 지금 값 = 구조물 표 한 줄(`resolveCatalog` · 정의 반영 ⊕ 처음값 ⊕ 부품 끔) * · 반영에는 처음 값과 다른 칸만 남김(같으면 지움 = 「기본」) · [처음 값으로] = 그 종류 반영 지움 * · 목록과 enabled 는 함께 씀 — enabled = 목록 both 일 때만 참(옛 짝 · 브레인 322-1 답 ①) * 화면 그리기 없음 — 표 · 종류별 · 부품 탭이 이 함수들로 사본을 고침. * ========================================================================== */ import PARTS from "../../common_util/common_util_structure_parts.json" with { type: "json" }; import { resolveCatalog, type CatalogDefinition, type CatalogRow, } from "../../common_util/common_util_structure_catalog"; import type { StructDef } from "./M02_SO_Def_Model"; export type ReflectField = "목록" | "횡단도" | "카드이름" | "형상" | "3D"; type Reflect = Partial> & { 형상칸?: Record }; interface Part { 이름: string; "읽는 칸": string[]; 자리꼴: string[]; 정본: string[]; } type Branch = Exclude; export const PART_BOX = (PARTS as unknown as { parts: Record> }).parts; const START = (PARTS as unknown as { 처음값: Record }).처음값; /** 칸 → 표 줄 열쇠. */ const ROW_KEY: Record = { 목록: "list", 횡단도: "station", 카드이름: "title", 형상: "shape", "3D": "model3d", }; export const reflectOf = (def: StructDef): Record => ((def as { 반영?: Record }).반영 ??= {}); export const partsOff = (def: StructDef): Record => ((def as { 부품?: Record }).부품 ??= {}); /** 사본 전체 표 — 종류마다 한 줄. */ export const rowsOf = (def: StructDef): Map => resolveCatalog(def as unknown as CatalogDefinition); /** 칸 지금 값. */ export const valueOf = (row: CatalogRow, field: ReflectField): string => String(row[ROW_KEY[field]]); /** 반영이 없을 때(처음 값 · 목록은 enabled 짝) 그 칸 값. */ export function defaultOf(def: StructDef, typeId: string, field: ReflectField): string { const copy = { ...def, 반영: { ...reflectOf(def) } } as unknown as StructDef; const own = { ...(reflectOf(copy)[typeId] ?? {}) }; delete own[field]; reflectOf(copy)[typeId] = own; const row = rowsOf(copy).get(typeId); return row ? valueOf(row, field) : ""; } /** 반영에 적힌 칸인가(아니면 「기본」). */ export const isSet = (def: StructDef, typeId: string, field: ReflectField | "형상칸"): boolean => field in (reflectOf(def)[typeId] ?? {}); const dropEmpty = (def: StructDef, typeId: string): void => { const all = reflectOf(def); if (all[typeId] && !Object.keys(all[typeId]).length) delete all[typeId]; }; /** 칸 고침 — 처음 값과 같으면 반영에서 지움 · 목록이면 enabled 도(both 일 때만 참). */ export function setReflect( def: StructDef, typeId: string, field: ReflectField, value: string, ): void { const type = def.types.find((t) => t.type_id === typeId); if (field === "목록" && type) type.enabled = value === "both"; const own = (reflectOf(def)[typeId] ??= {}); delete own[field]; if (defaultOf(def, typeId, field) !== value) own[field] = value; dropEmpty(def, typeId); } /** 형상칸 한 칸 고침(form 글 · weep 참거짓) — 처음 값과 같으면 지움. */ export function setShapeOpt(def: StructDef, typeId: string, key: string, value: unknown): void { const own = (reflectOf(def)[typeId] ??= {}); const opts = { ...(own.형상칸 ?? {}) }; const start = START[typeId]?.형상칸?.[key]; if (value === start || value === "" || (value === false && start === undefined)) delete opts[key]; else opts[key] = value; if (Object.keys(opts).length) own.형상칸 = opts; else delete own.형상칸; dropEmpty(def, typeId); } /** [처음 값으로] — 그 종류 반영 지움 · enabled = 처음 목록 both 일 때만 참. */ export function resetRow(def: StructDef, typeId: string): void { delete reflectOf(def)[typeId]; const type = def.types.find((t) => t.type_id === typeId); if (type) type.enabled = START[typeId] ? START[typeId].목록 === "both" : type.enabled; } /** [저장] 전 — 모든 종류 enabled = 지금 목록 both 일 때만 참(목록 + enabled 함께 씀). */ export function syncEnabled(def: StructDef): void { const rows = rowsOf(def); for (const type of def.types) { const row = rows.get(type.type_id); if (row) type.enabled = row.list === "both"; } } /** 부품 켬끔 — 끔만 남김. */ export function setPartOn(def: StructDef, key: string, on: boolean): void { const off = partsOff(def); if (on) delete off[key]; else off[key] = false; } /** 못 숨김 칸 — 종류가 지금 쓰는 부품들이 읽는 칸 중 기본값도 비워 두는 뜻도 없는 옵션 * (숨기면 그림이 받을 값이 없음 · 검사 짝 `M02_SO_Def_Check`). */ export function readKeys(def: StructDef, typeId: string): Set { const row = rowsOf(def).get(typeId); if (!row) return new Set(); const type = def.types.find((t) => t.type_id === typeId); const bare = new Set( (type?.options ?? []) .filter((o) => (o.default === null || o.default === undefined) && !o.empty_means) .map((o) => o.key), ); const used: Array<[Branch, string]> = [ ["횡단도", row.station], ["카드이름", row.title], ["형상", row.shape], ["3D", row.model3d], ]; return new Set( used .flatMap(([branch, id]) => PART_BOX[branch][id]?.["읽는 칸"] ?? []) .filter((key) => bare.has(key)), ); } /** 그 갈래 부품을 지금 쓰는 종류들. */ export function usersOf(def: StructDef, branch: Branch, id: string): CatalogRow[] { const field = ROW_KEY[branch]; return [...rowsOf(def).values()].filter((row) => row[field] === id); } /** 정의 「표시」 숨김 키(그 종류). */ export const hiddenKeys = (def: StructDef, typeId: string): Set => new Set( Object.entries(def.표시[typeId] ?? {}) .filter(([, shown]) => shown === false) .map(([key]) => key), ); /** 옵션 키들 보임 켬끔 — 정의 옵션인 키만(숨김 = false 만 남김). */ export function setShown( def: StructDef, typeId: string, keys: readonly string[], on: boolean, ): void { const type = def.types.find((t) => t.type_id === typeId); const known = new Set((type?.options ?? []).map((o) => o.key)); const row = (def.표시[typeId] ??= {}); for (const key of keys.filter((k) => known.has(k))) { if (on) delete row[key]; else row[key] = false; } if (!Object.keys(row).length) delete def.표시[typeId]; }