import { INDEX_CONTENDED, LETTERS_EXHAUSTED, ROLE_MISSING, ROW_SECTION_MISSING, alreadyInState, foreignWithoutReason, malformedRow, rowAdded, stateChanged, unboundLetter, unknownState, } from "../strings/index.strings.ts"; import { readFileSync, writeFileSync } from "node:fs"; import { indexedLetters } from "../inspectors/index.inspector.ts"; const ROW = "| "; const SECTION = "═"; const SECTION_WORD = "INDEX"; const ACTIVE_STATE = "ACTIVE"; const UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const LOWER = "abcdefghijklmnopqrstuvwxyz"; export const SEAT_STATES: readonly string[] = ["ACTIVE", "INACTIVE", "INVOKED"]; export interface TransitionRequest { readonly absolute: string; readonly letter: string; readonly state: string; readonly by: string; readonly warrant: string | null; } const refused = function refused(message: string): IndexOutcome { return { code: 2, message }; }; const rowLetter = function rowLetter(line: string): string { const cells = line.split("|"); return (cells[1] ?? "").trim(); }; export const runTransition = function runTransition(request: TransitionRequest): IndexOutcome { const foreign = request.letter !== request.by; if (foreign && (request.warrant === null || request.warrant.trim().length === 0)) { return refused(foreignWithoutReason(request.by, request.letter)); } if (!SEAT_STATES.includes(request.state)) { return refused(unknownState(request.state, SEAT_STATES)); } const source = readFileSync(request.absolute, "utf8"); const lines = source.split("\n"); const at = lines.findIndex((line) => line.startsWith(ROW) && rowLetter(line) === request.letter); if (at === -1) { return refused(unboundLetter(request.letter)); } const cells = (lines[at] ?? "").split("|"); if (cells.length < 5) { return refused(malformedRow(request.letter)); } const current = (cells[3] ?? "").trim(); if (current === request.state) { return { code: 0, message: alreadyInState(request.letter, request.state) }; } cells[3] = ` ${request.state} `; lines[at] = cells.join("|"); if (foreign) { lines.splice( at + 1, 0, `> ${request.letter} moved to ${request.state} by ${request.by} under ${String(request.warrant)}`, ); } const witness = readFileSync(request.absolute, "utf8"); if (witness !== source) { return refused(INDEX_CONTENDED); } writeFileSync(request.absolute, lines.join("\n"), "utf8"); return { code: 0, message: stateChanged(request.letter, request.state, foreign ? request.by : null) }; }; export interface IndexRequest { readonly absolute: string; readonly role: string; } export interface IndexOutcome { readonly code: number; readonly message: string; } const candidates = function candidates(): string[] { const out: string[] = []; for (const first of UPPER) { out.push(first); } for (const first of UPPER) { for (const second of LOWER) { out.push(`${first}${second}`); } } for (const first of UPPER) { for (const second of UPPER) { for (const third of LOWER) { out.push(`${first}${second}${third}`); } } } return out; }; const shortestFree = function shortestFree(taken: ReadonlySet): string | null { for (const candidate of candidates()) { if (!taken.has(candidate)) { return candidate; } } return null; }; const lastRowIn = function lastRowIn(lines: readonly string[]): number { let opened = false; let at = -1; for (let index = 0; index < lines.length; index += 1) { const line = lines[index] ?? ""; const trimmed = line.trim(); if (!opened) { if (trimmed.startsWith(SECTION) && trimmed.includes(SECTION_WORD)) { opened = true; } continue; } if (trimmed.startsWith(SECTION)) { break; } if (line.startsWith(ROW)) { at = index; } } return at; }; export const runIndex = function runIndex(request: IndexRequest): IndexOutcome { const role = request.role.trim(); if (role.length === 0) { return refused(ROLE_MISSING); } const source = readFileSync(request.absolute, "utf8"); const lines = source.split("\n"); const at = lastRowIn(lines); if (at === -1) { return refused(ROW_SECTION_MISSING); } const letter = shortestFree(indexedLetters(source).letters); if (letter === null) { return refused(LETTERS_EXHAUSTED); } const written = [...lines.slice(0, at + 1), `| ${letter} | ${role} | ${ACTIVE_STATE} |`, ...lines.slice(at + 1)]; const witness = readFileSync(request.absolute, "utf8"); if (witness !== source) { return refused(INDEX_CONTENDED); } writeFileSync(request.absolute, written.join("\n"), "utf8"); return { code: 0, message: rowAdded(letter, role) }; };