import type { PerAnimeDataPoint } from './StackedTrendChart'; const HIDDEN_TITLES_KEY = 'subminer-stats-trends-hidden-titles'; const MAX_TITLES_KEY = 'subminer-stats-trends-max-titles'; const MAX_TITLES_MODE_KEY = 'subminer-stats-trends-max-titles-mode'; const SHOW_EMPTY_DAYS_KEY = 'subminer-stats-trends-show-empty-days'; export const MAX_TITLES_OPTIONS = [3, 5, 7, 10] as const; // How the per-chart limit picks which titles survive: 'recent' keeps the most // recently active titles, 'total' keeps the highest cumulative totals. 'recent' // is listed first so it heads the dropdown. export type MaxTitlesMode = 'total' | 'recent'; export const MAX_TITLES_MODES: readonly MaxTitlesMode[] = ['recent', 'total']; // First-run defaults: show the 7 most recently active titles per chart. const DEFAULT_MAX_TITLES = 7; const DEFAULT_MAX_TITLES_MODE: MaxTitlesMode = 'recent'; const ALL_TITLES_STORED_VALUE = 'all'; function getStorage(): Storage | null { try { return globalThis.localStorage ?? null; } catch { return null; } } export function loadHiddenTitles(): Set { try { const raw = getStorage()?.getItem(HIDDEN_TITLES_KEY); const parsed: unknown = raw ? JSON.parse(raw) : []; if (!Array.isArray(parsed)) return new Set(); return new Set(parsed.filter((value): value is string => typeof value === 'string')); } catch { return new Set(); } } export function saveHiddenTitles(hidden: ReadonlySet): void { try { getStorage()?.setItem(HIDDEN_TITLES_KEY, JSON.stringify([...hidden])); } catch { // Storage can be blocked in private/restricted contexts; keep the in-memory choice. } } // Returns the persisted per-chart limit: a specific count, or null for "All". // Absent/garbage storage falls back to the default count (never null), so a // first run shows the default cap rather than every title. "All" is stored // explicitly so it round-trips instead of collapsing back to the default. export function loadMaxTitles(): number | null { try { const raw = getStorage()?.getItem(MAX_TITLES_KEY); if (raw === ALL_TITLES_STORED_VALUE) return null; const value = Number(raw); return (MAX_TITLES_OPTIONS as readonly number[]).includes(value) ? value : DEFAULT_MAX_TITLES; } catch { return DEFAULT_MAX_TITLES; } } export function saveMaxTitles(value: number | null): void { try { getStorage()?.setItem(MAX_TITLES_KEY, value === null ? ALL_TITLES_STORED_VALUE : String(value)); } catch { // Storage can be blocked in private/restricted contexts; keep the in-memory choice. } } export function loadMaxTitlesMode(): MaxTitlesMode { try { const raw = getStorage()?.getItem(MAX_TITLES_MODE_KEY); if (raw === 'recent' || raw === 'total') return raw; return DEFAULT_MAX_TITLES_MODE; } catch { return DEFAULT_MAX_TITLES_MODE; } } export function saveMaxTitlesMode(mode: MaxTitlesMode): void { try { getStorage()?.setItem(MAX_TITLES_MODE_KEY, mode); } catch { // Storage can be blocked in private/restricted contexts; keep the in-memory choice. } } // Whether trend charts zero-fill empty calendar buckets. Defaults to true so a // first run shows the calendar-accurate view. export function loadShowEmptyDays(): boolean { try { return getStorage()?.getItem(SHOW_EMPTY_DAYS_KEY) !== 'false'; } catch { return true; } } export function saveShowEmptyDays(show: boolean): void { try { getStorage()?.setItem(SHOW_EMPTY_DAYS_KEY, show ? 'true' : 'false'); } catch { // Storage can be blocked in private/restricted contexts; keep the in-memory choice. } } export function buildAnimeVisibilityOptions(datasets: PerAnimeDataPoint[][]): string[] { const totals = new Map(); for (const dataset of datasets) { for (const point of dataset) { totals.set(point.animeTitle, (totals.get(point.animeTitle) ?? 0) + point.value); } } return [...totals.entries()] .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) .map(([title]) => title); } export function filterHiddenAnimeData( data: PerAnimeDataPoint[], hiddenAnime: ReadonlySet, ): PerAnimeDataPoint[] { if (hiddenAnime.size === 0) { return data; } return data.filter((point) => !hiddenAnime.has(point.animeTitle)); } export function pruneHiddenAnime( hiddenAnime: ReadonlySet, availableAnime: readonly string[], ): Set { const availableSet = new Set(availableAnime); return new Set([...hiddenAnime].filter((title) => availableSet.has(title))); }