import { EmbedBuilder } from "discord.js"; import { PollState, VoteEntry, Nation } from "@types"; import { Config } from "@systems/config"; import { PollLayout, PollRowContext, PollEmbedOptions } from "@ui/types"; import path from "path"; import fs from "fs"; import { Runtime } from "@systems/runtime"; // ─── Runtime ────────────────────────────────────────────────────────────────── Runtime.phase("restore", () => restoreLayout(), { name: "PollUI.restoreLayout" }); // ─── Layout registry ────────────────────────────────────────────────────────── const _layouts = new Map(); let _activeLayout: PollLayout; export function registerLayout(layout: PollLayout): void { _layouts.set(layout.name, layout); if (!_activeLayout) _activeLayout = layout; // first registered = default } function isPollLayout(obj: any): obj is PollLayout { return obj?.name && obj?.description && typeof obj?.buildEmbed === "function" && typeof obj?.formatRow === "function" && typeof obj?.buildContext === "function"; } export function discoverLayouts(): void { const layoutsDir = path.join(__dirname, "layouts"); if (!fs.existsSync(layoutsDir)) return; const files = fs.readdirSync(layoutsDir) .filter((f) => f.endsWith(".ts") || f.endsWith(".js")) .sort(); // consistent order — default.ts loads before side-by-side.ts for (const file of files) { try { const mod = require(path.join(layoutsDir, file)); for (const exported of Object.values(mod)) { if (isPollLayout(exported)) { registerLayout(exported); console.log(`[PollUI] Registered layout: ${(exported as PollLayout).name}`); } } } catch (err) { console.error(`[PollUI] Failed to load layout ${file}:`, err); } } } function restoreLayout() { const savedLayout = Config.get({ section: "poll", key: "layout" }); if (savedLayout && _layouts.has(savedLayout)) { _activeLayout = _layouts.get(savedLayout)!; console.log(`[PollUI] Restored layout: ${savedLayout}`); } } // Auto-discover at module load time Config.load(); discoverLayouts(); restoreLayout(); // ─── Dispatcher ─────────────────────────────────────────────────────────────── function activeLayout(): PollLayout { return _activeLayout; } export const PollUI = { buildEmbed(state: PollState, options?: PollEmbedOptions): EmbedBuilder { return activeLayout().buildEmbed(state, options); }, formatRow(entry: VoteEntry, context: PollRowContext): string { return activeLayout().formatRow(entry, context); }, buildContext( entries: VoteEntry[], nation: Nation, options?: { showNationEmoji?: boolean } ): PollRowContext { return activeLayout().buildContext(entries, nation, options); }, setLayout(name: string): boolean { const layout = _layouts.get(name); if (!layout) return false; _activeLayout = layout; return true; }, layouts(): { name: string; description: string }[] { return [..._layouts.values()].map((l) => ({ name: l.name, description: l.description, })); }, register: registerLayout, };