/**
 * Translate a persistence failure into a user-facing notification (spec §10 →
 * "the user is told when a save fails rather than losing data silently").
 *
 * Pure and framework-free so the wording is unit-tested directly. The mapping
 * encodes the product's actionability rule (Carbon: a user action is mandatory
 * on an error message — but only the user's *own* action belongs in the copy):
 *
 *   - A failure the user CAN resolve (storage full) → tell them the step to
 *     take, and carry no `detail` (there is nothing for them to report).
 *   - A failure the user CANNOT resolve (storage blocked, an unexpected DB
 *     error) → explain it plainly AND attach diagnostic `detail` (the operation
 *     that failed + the underlying error) so it can be reported and traced.
 */

import { StorageQuotaError } from '../infrastructure/db';
import type { NotifyOptions } from '../stores/NotificationStore';

/** Which persistence operation failed — shapes both the wording and the trace. */
export type StorageOp = 'load' | 'save' | 'delete';

/** Compact, reportable description of an unexpected error for the `detail` field. */
function diagnostic(op: StorageOp, err: unknown): string {
  const base = `Storage ${op} failed`;
  if (err instanceof Error) {
    // Include the error name when it adds signal (DOMException subtypes, etc.).
    return err.name && err.name !== 'Error'
      ? `${base}: ${err.name}: ${err.message}`
      : `${base}: ${err.message}`;
  }
  return `${base}: ${String(err)}`;
}

/**
 * Map a thrown persistence error to the notification to show for it. `op` is the
 * operation that failed; `err` is whatever the adapter threw.
 */
export function storageErrorNotification(op: StorageOp, err: unknown): NotifyOptions {
  // Storage full — the one failure the user can act on themselves. No detail:
  // there's nothing to report, the next step is in the message.
  if (err instanceof StorageQuotaError) {
    return {
      kind: 'error',
      title: 'Storage full',
      message:
        "This snippet couldn't be saved because snippet storage is full. " +
        'Delete snippets you no longer need to free space, then edit again to retry.',
    };
  }

  // Couldn't read the library on startup — almost always blocked storage.
  if (op === 'load') {
    return {
      kind: 'error',
      title: "Couldn't open your library",
      message:
        "Astrolabe couldn't open local storage, so your saved snippets aren't available yet. " +
        'This usually means the browser is blocking storage — for example, private-browsing mode. ' +
        'Reload to try again.',
      detail: diagnostic(op, err),
    };
  }

  // An unexpected save/delete failure the user can't do anything about. Plain
  // explanation for them; diagnostic detail for whoever they report it to.
  const title = op === 'delete' ? "Couldn't delete the snippet" : "Couldn't save your changes";
  return {
    kind: 'error',
    title,
    message:
      'A storage error stopped Astrolabe from completing the last change, so it may not survive a reload. ' +
      'If this keeps happening, your browser may be blocking local storage (such as private-browsing mode).',
    detail: diagnostic(op, err),
  };
}

/**
 * The storage-failure notification for the per-record entity tiers other than
 * snippets (datasets, themes, fonts), parameterized by entity noun. Mirrors the
 * snippet-flavored `storageErrorNotification` above — quota detection, the startup
 * `load` failure, and the generic save/delete failure — but with the entity's own
 * noun so a dataset error never reads "snippet". Snippets keep the bespoke function
 * above (its "your library" / "your changes" framing is tuned to the silent
 * save-on-edit model). `noun` is the lowercase singular ("dataset").
 */
export function entityStorageErrorNotification(
  noun: string,
  op: StorageOp,
  err: unknown,
): NotifyOptions {
  const Noun = noun.charAt(0).toUpperCase() + noun.slice(1);
  const plural = `${noun}s`;
  const detail =
    err instanceof Error ? `${Noun} ${op} failed: ${err.name}: ${err.message}` : String(err);

  // Storage full — the one failure the user can act on. No detail: the next step
  // is in the message. (Quota is whole-origin; per-tier framing is a parked UX
  // nuance — see docs/ux-second-pass.md.)
  if (err instanceof StorageQuotaError) {
    return {
      kind: 'error',
      title: 'Storage full',
      message:
        `This ${noun} couldn't be saved because ${noun} storage is full. ` +
        `Delete ${plural} you no longer need to free space, then try again.`,
    };
  }

  // Couldn't read this tier on startup — almost always blocked storage.
  if (op === 'load') {
    return {
      kind: 'error',
      title: `Couldn't open your ${plural}`,
      message:
        `Astrolabe couldn't open local storage, so your saved ${plural} aren't available yet. ` +
        'This usually means the browser is blocking storage — for example, private-browsing mode. ' +
        'Reload to try again.',
      detail,
    };
  }

  return {
    kind: 'error',
    title: op === 'delete' ? `Couldn't delete the ${noun}` : `Couldn't save the ${noun}`,
    message:
      `A storage error stopped Astrolabe from completing the last ${noun} change, so it may not ` +
      'survive a reload. If this keeps happening, your browser may be blocking local storage.',
    detail,
  };
}
