/**
 * App startup orchestration.
 *
 * Loads the snippet library from IndexedDB into the store, then wires the
 * persistence subscribers. Called once from main.tsx; the UI renders immediately
 * and fills in when hydration completes. An empty library is NOT seeded with a
 * placeholder — the workspace shows the onboarding canvas instead (spec §02 →
 * First-Run & Empty Workspace).
 */

import type { Snippet } from '@core/snippet';
import type { Dataset } from '@core/dataset';
import type { CustomTheme } from '@core/custom-theme';
import type { FontAsset } from '@core/font-asset';
import { loadSnippets } from '../infrastructure/snippet-store';
import { loadDatasets } from '../infrastructure/dataset-store';
import { loadCustomThemes } from '../infrastructure/theme-store';
import { loadFonts } from '../infrastructure/font-store';
import { registerFontAssets } from '../infrastructure/font-faces';
import {
  entityStorageErrorNotification,
  storageErrorNotification,
} from '../services/storage-errors';
import { notify } from '../stores/NotificationStore';
import { useSnippetStore } from '../stores/SnippetStore';
import { useDatasetStore } from '../stores/DatasetStore';
import { useCustomThemeStore } from '../stores/CustomThemeStore';
import { useFontStore } from '../stores/FontStore';
import { wireSnippetPersistence } from './snippet-persistence';
import { wireDatasetPersistence } from './dataset-persistence';
import { wireThemePersistence } from './theme-persistence';
import { wireFontPersistence } from './font-persistence';
import { startRouting } from '../modals/UrlStateSync';
import { startEventRouter } from './EventRouter';

let started = false;

export async function initApp(): Promise<void> {
  if (started) return; // idempotent — guard against double-invocation (StrictMode)
  started = true;

  // If storage can't be opened (private-browsing, blocked storage), don't fail
  // startup silently: warn, then run this session in memory so the app is still
  // usable. An empty result — whether genuinely empty or a failed read — leaves
  // the library empty and the onboarding canvas takes over (spec §02).
  let snippets: Snippet[] = [];
  try {
    snippets = await loadSnippets();
  } catch (err) {
    notify(storageErrorNotification('load', err));
  }

  // Load the dataset library too (no first-run seed — datasets are user-created).
  // A failure here is surfaced; the app stays usable with an empty dataset library.
  let datasets: Dataset[] = [];
  try {
    datasets = await loadDatasets();
  } catch (err) {
    notify(entityStorageErrorNotification('dataset', 'load', err));
  }

  // Custom chart themes (spec §04 → Chart theme). Same failure posture: the
  // picker just shows no custom entries, and a persisted custom selection
  // renders as the house style until its record is available.
  let themes: CustomTheme[] = [];
  try {
    themes = await loadCustomThemes();
  } catch (err) {
    notify(entityStorageErrorNotification('theme', 'load', err));
  }

  // User-uploaded font faces (scope doc §4). Same failure posture: a font that
  // can't be loaded just won't be offered or rendered, falling back to the stack's
  // generic family.
  let fonts: FontAsset[] = [];
  try {
    fonts = await loadFonts();
  } catch (err) {
    notify(entityStorageErrorNotification('font', 'load', err));
  }

  useSnippetStore.getState().hydrate(snippets);
  useDatasetStore.getState().hydrate(datasets);
  useCustomThemeStore.getState().hydrate(themes);
  useFontStore.getState().hydrate(fonts);

  // Register the loaded faces on document.fonts BEFORE the first chart render so
  // the renderer's font gate (ensureFontsLoaded) can resolve them like the roster.
  registerFontAssets(fonts);

  // Wire persistence AFTER hydrate so write-through's baseline is the loaded set
  // — otherwise it would redundantly re-save every record on each startup.
  wireSnippetPersistence();
  wireDatasetPersistence();
  wireThemePersistence();
  wireFontPersistence();

  // Routing starts AFTER hydrate so the on-load hash restore can resolve snippet
  // / dataset ids against the loaded stores (spec §01E, docs/architecture/04).
  startRouting();
  // Global keyboard shortcuts (spec §01D). Order after routing so a shortcut that
  // navigates (new snippet, toggle Datasets) writes through a started router.
  startEventRouter();
}
