/**
 * Storage composition summarization (spec §02 → Storage Monitor; docs/architecture/02 §6).
 *
 * Portable core: no browser APIs, no React. The Storage Monitor is **not** a "used
 * of quota" gauge — the browser's `quota` from `navigator.storage.estimate()` is a
 * padded, browser-decided approximation, not a real free-space figure, so a budget
 * fraction would be false precision (web.dev → storage-for-the-web). Instead we show
 * what storage is *made of*: Snippets, Datasets, and App (everything else — the
 * precached app shell + IndexedDB overhead). This module turns three measured byte
 * counts into ordered segments; the math lives here so it is trivially testable.
 *
 * The only reliable figure from the estimate is `usage` (bytes actually stored for
 * the whole origin). Snippet and dataset bytes we measure ourselves, so
 * `App = usage − snippets − datasets`. When the estimate is missing we still show
 * snippets + datasets from our own data.
 */

/** A category of stored data and its measured size in bytes. */
interface StorageSegment {
  key: 'snippets' | 'datasets' | 'app';
  /** User-facing label, centralized here so the component stays presentational. */
  label: string;
  bytes: number;
}

/** Measured inputs: snippet/dataset bytes (always known) + origin usage (when the API is present). */
export interface StorageInput {
  /** Whole-origin bytes from `navigator.storage.estimate().usage`, if available. */
  usageBytes?: number;
  /** Summed serialized size of all snippets. */
  snippetBytes: number;
  /** Summed size of all datasets (each `Dataset` carries its byte `size`). */
  datasetBytes: number;
}

/** Presentation-ready storage composition. */
export interface StorageComposition {
  /** Ordered segments to display: Snippets, Datasets, then App when measurable. */
  segments: StorageSegment[];
  /** Sum of the segments shown — origin `usage` when measured, else snippets+datasets. */
  totalBytes: number;
  /**
   * True when the origin estimate was available, so the `app` segment (the precached
   * shell + overhead) is meaningful and `totalBytes` is whole-origin usage. False when
   * the Storage Manager API is absent — we still show snippets + datasets, and
   * `totalBytes` is just their sum.
   */
  originMeasured: boolean;
}

/**
 * The monitor stays hidden until the user's own data — snippets + datasets — reaches
 * this size (spec §02). Below it storage is noise (nothing to manage), and the bar
 * would be dominated by the immovable precached app shell anyway. Deliberately keyed
 * off *user* bytes, not total: the ~precache baseline is roughly constant, so gating
 * on total would make the monitor always-visible and the breakdown unbalanced.
 */
export const STORAGE_MONITOR_MIN_USER_BYTES = 10 * 1024 * 1024;

/** A finite, non-negative byte count, or null when the value is unusable. */
function usableBytes(n: number | undefined): number | null {
  if (typeof n !== 'number' || !Number.isFinite(n) || n < 0) return null;
  return n;
}

/** A finite, non-negative byte count; junk degrades to 0. */
function nonNeg(n: number): number {
  return usableBytes(n) ?? 0;
}

/**
 * Decompose measured storage into ordered segments. The `app` segment (everything
 * beyond our snippets+datasets) is included only when the origin `usage` is available
 * and at least our measured data — a smaller estimate is the browser's approximation
 * lagging, not real, so we fall back to a snippets+datasets-only view.
 */
export function summarizeStorage(input: StorageInput): StorageComposition {
  const snippets = nonNeg(input.snippetBytes);
  const datasets = nonNeg(input.datasetBytes);
  const usage = usableBytes(input.usageBytes);
  const known = snippets + datasets;

  const segments: StorageSegment[] = [
    { key: 'snippets', label: 'Snippets', bytes: snippets },
    { key: 'datasets', label: 'Datasets', bytes: datasets },
  ];

  if (usage !== null && usage >= known) {
    segments.push({ key: 'app', label: 'App', bytes: usage - known });
    return { segments, totalBytes: usage, originMeasured: true };
  }
  return { segments, totalBytes: known, originMeasured: false };
}

/** UTF-8 byte length of a value's JSON serialization (0 when it can't be serialized). */
export function jsonByteSize(value: unknown): number {
  try {
    return new TextEncoder().encode(JSON.stringify(value) ?? '').length;
  } catch {
    return 0;
  }
}

const BYTE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;

/**
 * Render a byte count as a human-readable string using binary (1024) units.
 * Bytes are shown as a whole number with the `B` unit; KB and up get one
 * decimal place (e.g. `1.5 MB`). Negative/non-finite inputs degrade to `0 B`.
 */
export function humanizeBytes(n: number): string {
  if (!Number.isFinite(n) || n < 0) return '0 B';
  if (n < 1024) return `${Math.round(n)} B`;

  let value = n;
  let unit = 0;
  while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
    value /= 1024;
    unit += 1;
  }
  return `${value.toFixed(1)} ${BYTE_UNITS[unit]}`;
}
