/**
 * Notifications — the app's non-blocking feedback channel (toasts).
 *
 * The sibling of ConfirmStore: ConfirmStore is the *blocking* layer (the user
 * must choose before continuing), this is the *non-blocking* one (an outcome is
 * announced and the user carries on). Per the design language "non-blocking
 * outcomes are toasts" (docs/architecture/09 → Toasts); per spec §10 a failed
 * save must *tell the user* rather than lose data silently.
 *
 * A notification's `kind` carries Carbon's status meaning (error/warning/
 * success/info) and drives both colour and urgency. The product distinction —
 * can the user fix it or not — is encoded in the copy plus the optional
 * `detail`: a user-fixable failure puts the next step in `message` and omits
 * `detail`; a failure the user can't fix carries diagnostic `detail` so the
 * problem can be reported (see services/storage-errors).
 *
 * The store holds only the list + add/dismiss; timed auto-dismiss for the
 * positive kinds lives in the Toaster component, keeping this store pure and
 * deterministic to test. Callable from non-React code (orchestration) via the
 * imperative `notify` export, exactly like `confirm`.
 */

import { create } from 'zustand';

/** Carbon status levels: colour carries meaning (docs/architecture/09). */
export type NotificationKind = 'error' | 'warning' | 'success' | 'info';

export interface NotifyOptions {
  kind: NotificationKind;
  /** What happened, in a few words (Carbon: "tell users what stopped"). */
  title: string;
  /** One or two sentences: the consequence and, when fixable, the next step. */
  message: string;
  /**
   * Technical diagnostics for a failure the user cannot fix, surfaced so the
   * issue can be reported and traced. Omit for user-actionable or positive
   * notifications — there is nothing for the user to report.
   */
  detail?: string;
  /**
   * Optional inline call-to-action rendered as a button in the toast (Carbon
   * notification action). The toast dismisses after the handler runs. Used for
   * e.g. the service-worker "Reload to update" prompt (web.dev → council).
   */
  action?: { label: string; onClick: () => void };
  /**
   * When true, the toast never auto-dismisses — it waits for the user (a Reload
   * prompt or an offer the user must see). Mirrors how errors/warnings persist;
   * lets a positive/info kind opt out of the auto-dismiss timer.
   */
  durable?: boolean;
}

export interface Notification extends NotifyOptions {
  /** Stable id for React keys and dismissal. */
  id: string;
}

/** True when two notifications say the same thing — used to coalesce repeats. */
function sameContent(a: NotifyOptions, b: NotifyOptions): boolean {
  return a.kind === b.kind && a.title === b.title && a.message === b.message;
}

export interface NotificationState {
  notifications: Notification[];
  /**
   * Announce a notification; returns its id. An identical one already showing is
   * coalesced (its id is returned, nothing is appended) so a repeatedly-failing
   * auto-save can't stack a tower of the same toast.
   */
  notify: (options: NotifyOptions) => string;
  /** Dismiss one notification by id (the close button, or auto-dismiss). */
  dismiss: (id: string) => void;
  /** Remove all notifications. */
  clear: () => void;
}

export const useNotificationStore = create<NotificationState>((set, get) => ({
  notifications: [],

  notify: (options) => {
    const existing = get().notifications.find((n) => sameContent(n, options));
    if (existing) return existing.id;
    const id = crypto.randomUUID();
    set((s) => ({ notifications: [...s.notifications, { ...options, id }] }));
    return id;
  },

  dismiss: (id) => set((s) => ({ notifications: s.notifications.filter((n) => n.id !== id) })),

  clear: () => set({ notifications: [] }),
}));

/** Imperative entry point for non-React callers (orchestration, services). */
export const notify = (options: NotifyOptions): string =>
  useNotificationStore.getState().notify(options);
