/**
 * Modal registry (docs/architecture/03 → Layer 1).
 *
 * One **metadata** entry per feature modal — the single source of truth the
 * coordinator, URL sync, and shell read, so adding a modal is one entry here,
 * one row in the shell's `MODAL_COMPONENTS` map, and its component — rather than
 * edits scattered across the shell, URL sync, and close logic. The component
 * map lives in `components/ModalShell` (its only consumer), NOT here: a registry
 * that imported components would close an import cycle (coordinator → registry →
 * component → coordinator).
 *
 * Divergence from the arch sketch's optional generic footer: each modal renders
 * its OWN action row inside its body (the Datasets manager is multi-view, so a
 * single shell-level Save/Cancel doesn't fit). The registry therefore omits
 * `hasError`/`getError` (validity is the modal's own concern) and keeps only
 * `getState` for unsaved-change detection on close. The registry is a partial
 * map — only the implemented ones are registered here. Settings is deliberately NOT
 * a modal — preferences are distributed to per-pane disclosure popovers (spec §07;
 * see components/SettingsPopover).
 */

import type { ActiveModal, ModalName } from './types';
import { customThemeIdOf } from '@core/vega-themes';
import { useAppStore } from '../stores/AppStore';
import { useChartBuilderStore } from '../stores/ChartBuilderStore';
import { selectIsDraftDirty, useCustomThemeStore } from '../stores/CustomThemeStore';
import { useDatasetStore } from '../stores/DatasetStore';
import { useExtractStore } from '../stores/ExtractStore';

export interface ModalConfig {
  name: ModalName;
  /** Header title (literal for now; an i18n key once strings are centralized). */
  title: string;
  /** Initialize transient state on open. `arg` carries an optional sub-target. */
  init?: (arg?: string) => void;
  /**
   * Serializable snapshot of in-progress edits for unsaved-change detection.
   * Return null when there is nothing to lose (browsing, or no open form) so
   * closing doesn't prompt. Omit entirely for modals that apply immediately.
   */
  getState?: () => Record<string, unknown> | null;
  /** Whether the modal is reflected in the URL hash (navigable). */
  isUrlNavigable?: boolean;
  /**
   * Whether a backdrop (click-outside) closes the modal. Defaults to `true`. Set
   * `false` for modals holding in-progress work an accidental click shouldn't
   * discard (the Chart Builder) — Escape and the close button still dismiss.
   */
  dismissOnBackdrop?: boolean;
}

const MODAL_REGISTRY: Partial<Record<ModalName, ModalConfig>> = {
  // Navigable, multi-view manager. The snapshot captures only the open create/edit
  // form, so browsing list↔detail never trips a false discard prompt.
  datasets: {
    name: 'datasets',
    title: 'Datasets',
    isUrlNavigable: true,
    init: (datasetId) => useDatasetStore.getState().select(datasetId ? Number(datasetId) : null),
    getState: () => {
      const s = useDatasetStore.getState();
      return s.view === 'new' || s.view === 'edit' ? { form: s.form } : null;
    },
  },

  // Opened from the snippet editor by `services/extract-action`, which seeds the
  // store with the focused view's inline data (`begin`) *before* opening — so no
  // `init` here, which would re-read top-level and clobber the view-scoped capture.
  extract: {
    name: 'extract',
    title: 'Extract to Dataset',
    getState: () => ({ name: useExtractStore.getState().name }),
  },

  // Opened from a dataset's "Build Chart" action (`arg` is its id) or from the
  // library / onboarding doors with no arg — init(null) then picks the most
  // recently modified dataset (§06 → Opening). Loads the dataset and
  // pre-populates a smart default config (§06). Applies on Create
  // (a new snippet), so there is nothing transient to lose on close — no getState.
  // Backdrop dismissal is off: the config is real in-progress work, and a stray
  // click outside this large surface shouldn't throw it away (Escape/× still close).
  chartBuilder: {
    name: 'chartBuilder',
    title: 'Chart Builder',
    isUrlNavigable: true,
    dismissOnBackdrop: false,
    init: (datasetId) => useChartBuilderStore.getState().init(datasetId ? Number(datasetId) : null),
  },

  // Opened from the chart-theme picker's "Edit themes…" entry. Re-seeds the open
  // draft from its saved record on open (clean baseline), keeping whichever theme
  // was last edited — or the active custom selection — in view. The snapshot is
  // the dirty draft only, so browsing themes never trips a false discard prompt.
  // Backdrop dismissal is off: config edits are real in-progress work.
  themeBuilder: {
    name: 'themeBuilder',
    title: 'Theme Builder',
    dismissOnBackdrop: false,
    init: () => {
      const store = useCustomThemeStore.getState();
      const activeCustomId = customThemeIdOf(useAppStore.getState().chartTheme);
      const id =
        store.selectedId ??
        (activeCustomId !== null && store.themes.some((t) => t.id === activeCustomId)
          ? activeCustomId
          : (store.themes[0]?.id ?? null));
      store.select(id);
    },
    getState: () => {
      const s = useCustomThemeStore.getState();
      return selectIsDraftDirty(s) ? { selectedId: s.selectedId, draft: s.draft } : null;
    },
  },

  // Pure info modals — no state, no validity check, no discard prompt on close.
  // `about` is URL-navigable (reload-restore); `donate` is not (spec §01E hash
  // table omits both, but `about` is still a permanent, bookmark-worthy surface).
  about: {
    name: 'about',
    title: 'About & Help',
  },
  // User-facing name is "Support" (feedback + the Ukraine-defense donation); the
  // id stays `donate` — its origin and still its primary accent CTA.
  donate: {
    name: 'donate',
    title: 'Support',
  },
};

export const getModalConfig = (name: ActiveModal): ModalConfig | undefined =>
  name ? MODAL_REGISTRY[name] : undefined;

export const getModalTitle = (name: ActiveModal): string => getModalConfig(name)?.title ?? '';
