/* ============================================================================= * B06_Section_UI_Page_Span_Control.ts * 구조물 **구간값(길이·전/후)** 제어 — 다단 단별 구간값과 유입·유출·집수정 구간값. * * `B06_Section_UI_Page_Station_Controls` 에서 떼어냈다(700줄 제한, 2026-09-02). * 세션 칸(`extraSpans` · 값 주인 · 472-5 5-D)과 캐시(design)를 함께 만지는 자리라 한 덩어리로 옮겼다. * 기준벽 · 집수정 구간값은 폼 칸(관 초안)이 정본 — 여기는 읽기만(433-19 · 444). * ========================================================================== */ import type { CrossDesign, CrossSection, StoredWallSpan } from "./B06_Section_Api_Fetch"; import { tierSpanOf, type SpanValues, type StructureSpanControl, } from "./B06_Section_UI_Cross_Culvert_Wire"; import * as CulvertConst from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { SessionCells } from "./B06_Section_Session_Cells"; /** 구간값 제어가 페이지·상위 제어기에서 받아 쓰는 창구. */ export interface SpanControlDeps { detail: () => { cross_sections: CrossSection[] } | null; refreshCard: (chainageM: number) => void; /** 연동 구조물의 소유 측점 — 값은 소유 측점 하나에만 담는다. */ ownerOf: (section: CrossSection) => CrossSection | null; /** 세션 칸(누가거리:벽키 → 구간값) — 읽고 쓰면 곧 칸. */ extraSpans: SessionCells; patchCachedDesign: (chainageM: number, patch: Partial) => void; } /** 구간값 제어기와 단별 구간값 조회를 만든다. */ export function createSpanControl(deps: SpanControlDeps): { control: StructureSpanControl; /** 이 측점의 단별 구간값 전부(세션 우선) — 상위 제어기가 payload에 실을 때 쓴다. */ tierSpansOf: (owner: CrossSection) => Record; spanKeyOf: (chainageM: number, wall: string) => string; } { const { extraSpans, patchCachedDesign, ownerOf } = deps; const spanKeyOf = (chainageM: number, wall: string): string => `${chainageM.toFixed(2)}:${wall}`; const storedTierSpan = (owner: CrossSection, wall: string): SpanValues => { const span = tierSpanOf(owner, wall); return { lengthM: Math.round((span.beforeM + span.afterM) * 10) / 10, beforeM: Math.round(span.beforeM * 10) / 10, afterM: Math.round(span.afterM * 10) / 10, }; }; /** 이 측점의 단별 구간값 전부(세션 우선) — 캐시·payload에 실을 모양으로. */ const tierSpansOf = (owner: CrossSection): Record => { const prefix = `${owner.chainage_m.toFixed(2)}:`; const result: Record = { ...(owner.design?.extra_spans ?? {}) }; extraSpans.forEach((value, key) => { if (!key.startsWith(prefix)) return; result[key.slice(prefix.length)] = { length_m: value.lengthM, before_m: value.beforeM, after_m: value.afterM, }; }); return result; }; const control: StructureSpanControl = { ownerOf, tierValuesFor: (section, key) => { const owner = ownerOf(section) ?? section; return extraSpans.get(spanKeyOf(owner.chainage_m, key)) ?? storedTierSpan(owner, key); }, updateTier: (section, key, patch) => { const owner = ownerOf(section); if (!owner) return; const mapKey = spanKeyOf(owner.chainage_m, key); const current = extraSpans.get(mapKey) ?? storedTierSpan(owner, key); const next = CulvertConst.applySpanPatch(current, patch); extraSpans.set(mapKey, next); // 캐시(design)에도 얹는다 — 링크 판정·3D가 순수 함수로 이 값을 읽는다. patchCachedDesign(owner.chainage_m, { extra_spans: tierSpansOf(owner) }); // 연장이 바뀌면 링크되는 옆 측점이 달라진다 — 옛 연장·새 연장을 합친 구간만. const reach = Math.max(current.beforeM, current.afterM, next.beforeM, next.afterM); for (const other of deps.detail()?.cross_sections ?? []) { if (Math.abs(other.chainage_m - owner.chainage_m) <= reach + 1e-9) { deps.refreshCard(other.chainage_m); } } }, valuesFor: (section, role) => { const owner = ownerOf(section); return owner ? CulvertConst.spanValuesOf(owner, role) : null; }, }; return { control, tierSpansOf, spanKeyOf }; }