import type { ProjectFile } from "./project"; import { flattenKeys } from "./geometry"; import { validateProject, type ValidationIssue } from "./validate"; export type MockTarget = { format: "mock-target"; version: "0.1.0"; layout: Array<{ keyId: string; x: number; y: number; rotation: number; w: number; h: number }>; idx: string[]; matrix: { rowPins: string[]; colPins: string[]; rows: number[]; cols: number[] }; keymaps: Array<{ name: string; keycodes: string[] }>; mapping: Array<{ number: number; keyId: string; row: number; col: number; label: string }>; }; export type GeneratorResult = { target?: MockTarget; errors: ValidationIssue[] }; /** Generates preview output after validation, without modifying the project. T-2/T-19. */ export function generateMockTarget(p: ProjectFile): GeneratorResult { const errors = validateProject(p).filter( (x) => x.severity === "blocksOutput" || x.severity === "blocksSave", ); if (!p.output.matrix) errors.push({ severity: "blocksOutput", code: "matrix-settings-missing", message: "Matrix rowPins and colPins must be configured", }); if (p.output.matrix?.kind && p.output.matrix.kind !== "matrix") errors.push({ severity: "blocksOutput", code: "unsupported-scan-mode", message: `${p.output.matrix.kind} scan mode is not supported`, }); if (errors.length) return { errors }; const selected = flattenKeys(p.phy, p.output.variantSelection), active = new Set(selected.map((x) => x.key.id)), order = p.idx.order.filter((id) => active.has(id)), matrix = p.output.matrix!; const rowIndex = new Map(matrix.rowPins.map((x, i) => [x, i])), colIndex = new Map(matrix.colPins.map((x, i) => [x, i])); const wiring = new Map(p.elc.keys.map((x) => [x.key, x])); const row: number[] = [], col: number[] = []; for (const id of order) { const w = wiring.get(id); if (!w || w.detect !== "contact") return { errors: [ { severity: "blocksOutput", code: "unsupported-detection", message: "Only contact matrix wiring is supported", ids: [id], }, ], }; const a = w.terminals.a, b = w.terminals.b; let r: number | undefined, c: number | undefined; if (rowIndex.has(a) && colIndex.has(b)) { r = rowIndex.get(a); c = colIndex.get(b); } else if (rowIndex.has(b) && colIndex.has(a)) { r = rowIndex.get(b); c = colIndex.get(a); } if (r === undefined || c === undefined) return { errors: [ { severity: "blocksOutput", code: "unsupported-matrix-wiring", message: "Wiring is not a supported row/column matrix", ids: [id], }, ], }; row.push(r); col.push(c); } const poses = new Map(selected.map((x) => [x.key.id, x])); const keymaps = p.map.layers.map((l) => ({ name: l.name, keycodes: order.map((id) => l.keymap.find((a) => a.keyId === id)!.keycode), })); const target: MockTarget = { format: "mock-target", version: "0.1.0", layout: order.map((id) => { const x = poses.get(id)!; return { keyId: id, x: x.x, y: x.y, rotation: x.rotation, w: x.key.size.w, h: x.key.size.h }; }), idx: order, matrix: { rowPins: [...matrix.rowPins], colPins: [...matrix.colPins], rows: row, cols: col }, keymaps, mapping: order.map((id, i) => ({ number: i, keyId: id, row: row[i]!, col: col[i]!, label: poses.get(id)!.key.labels?.find(Boolean) ?? "", })), }; return { target, errors: [] }; }