// 종목별 사용자 수평선(horizontal price lines) 저장소. // TradingView/Toss 식 그리기 도구의 가장 가벼운 형태 — 가격 라인만, per-code, localStorage. // 차트 자체는 라인 ID 를 모르고 좌표만 받음. 추가/삭제는 이 모듈을 통해서만. const KEY = (code: string) => `hlines:${code}`; export const HLINE_MAX = 10; export type HLine = { id: string; price: number; // 작성 시각 — 디버그/정렬용. UI 에선 노출 안 해도 됨. createdAt: string; }; function safeStorage(): Storage | null { if (typeof window === "undefined") return null; try { return window.localStorage; } catch { return null; } } export function readLines(code: string): HLine[] { const s = safeStorage(); if (!s) return []; try { const raw = s.getItem(KEY(code)); if (!raw) return []; const parsed = JSON.parse(raw) as unknown; if (!Array.isArray(parsed)) return []; const out: HLine[] = []; for (const item of parsed) { if ( item && typeof item === "object" && typeof (item as HLine).id === "string" && typeof (item as HLine).price === "number" && Number.isFinite((item as HLine).price) && (item as HLine).price > 0 ) { out.push({ id: (item as HLine).id, price: (item as HLine).price, createdAt: typeof (item as HLine).createdAt === "string" ? (item as HLine).createdAt : new Date().toISOString(), }); } } return out.slice(0, HLINE_MAX); } catch { return []; } } function writeRaw(code: string, lines: HLine[]): boolean { const s = safeStorage(); if (!s) return false; try { if (lines.length === 0) s.removeItem(KEY(code)); else s.setItem(KEY(code), JSON.stringify(lines.slice(0, HLINE_MAX))); return true; } catch { return false; } } // id 충돌 가능성이 거의 없는 short id. crypto.randomUUID 미지원 환경 폴백. function newId(): string { if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { return crypto.randomUUID(); } return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; } // price 같음 검사용 — 유효숫자 4 자리 정도 비교로 부동소수 오차 무시. function nearlyEqual(a: number, b: number): boolean { const denom = Math.max(Math.abs(a), Math.abs(b), 1); return Math.abs(a - b) / denom < 1e-4; } export function addLine(code: string, price: number): HLine[] { if (!Number.isFinite(price) || price <= 0) return readLines(code); const cur = readLines(code); // 같은 가격이 이미 있으면 중복 추가 안 함 — 라인이 안 보일 뿐 헷갈림 유발. if (cur.some((l) => nearlyEqual(l.price, price))) return cur; const next = [...cur, { id: newId(), price, createdAt: new Date().toISOString() }]; writeRaw(code, next); return next.slice(0, HLINE_MAX); } export function removeLine(code: string, id: string): HLine[] { const cur = readLines(code); const next = cur.filter((l) => l.id !== id); writeRaw(code, next); return next; } export function clearLines(code: string): HLine[] { writeRaw(code, []); return []; }