/**
 * URL hash as view-state (spec §01 → E "Navigation & Shareable URL State";
 * architecture §04 → "1. URL Hash as View-State"). The current view — selected
 * snippet, open Datasets modal, a specific dataset, the new-dataset form, or the
 * Chart Builder — is serialized into `location.hash` so every meaningful view is
 * shareable, bookmarkable, and reload-safe, and Back/Forward steps between states.
 *
 * This is the ONLY module that reads or writes `window.location` / `history`.
 * Parsing is a total pure function (no side effects, never throws — an unknown or
 * malformed hash degrades to the default snippets view). Writing is the only place
 * `history.replaceState` / `pushState` is called.
 *
 * Hash grammar (matches the spec table exactly):
 *
 *   snippets (default)  →  ''  (empty / absent)
 *   snippet             →  #snippet-<id>                  (id is an opaque string)
 *   datasets            →  #datasets
 *   dataset             →  #datasets/dataset-<id>         (id is a decimal number)
 *   dataset-new         →  #datasets/new
 *   dataset-build       →  #datasets/dataset-<id>/build
 *   build               →  #build                         (builder, no dataset yet)
 */

/** The serialized view. Snippet id is opaque; dataset id is the numeric id. */
export type ViewState =
  | { kind: 'snippets' } // empty hash
  | { kind: 'snippet'; snippetId: string } // #snippet-<id>
  | { kind: 'datasets' } // #datasets
  | { kind: 'dataset'; datasetId: number } // #datasets/dataset-<id>
  | { kind: 'dataset-new' } // #datasets/new
  | { kind: 'dataset-build'; datasetId: number } // #datasets/dataset-<id>/build
  | { kind: 'build' }; // #build — the Chart Builder with no dataset loaded yet

/**
 * Parse a raw hash (with or without the leading `#`) into a typed `ViewState`.
 * Total and pure: anything unrecognized or malformed degrades to the default
 * snippets view rather than throwing.
 */
export function parseHash(rawHash: string): ViewState {
  const hash = rawHash.replace(/^#/, '');
  if (hash === '') return { kind: 'snippets' };

  const snippet = /^snippet-(.+)$/.exec(hash);
  if (snippet) return { kind: 'snippet', snippetId: snippet[1] };

  if (hash === 'build') return { kind: 'build' };

  const parts = hash.split('/').filter(Boolean);
  if (parts[0] === 'datasets') {
    if (parts.length === 1) return { kind: 'datasets' };
    if (parts[1] === 'new') return { kind: 'dataset-new' };
    const m = /^dataset-(\d+)$/.exec(parts[1]);
    if (m) {
      const id = Number(m[1]);
      if (parts[2] === 'build') return { kind: 'dataset-build', datasetId: id };
      if (parts.length === 2) return { kind: 'dataset', datasetId: id };
    }
  }
  // Unknown / malformed hash → fall back to default rather than throwing.
  return { kind: 'snippets' };
}

/**
 * Serialize a `ViewState` into a hash string (round-trips with `parseHash`).
 * The exhaustive switch uses a `never`-typed default so a newly added `ViewState`
 * variant without a case becomes a compile-time error.
 */
export function serializeHash(view: ViewState): string {
  switch (view.kind) {
    case 'snippets':
      return '';
    case 'snippet':
      return `#snippet-${view.snippetId}`;
    case 'datasets':
      return '#datasets';
    case 'dataset':
      return `#datasets/dataset-${view.datasetId}`;
    case 'dataset-new':
      return '#datasets/new';
    case 'dataset-build':
      return `#datasets/dataset-${view.datasetId}/build`;
    case 'build':
      return '#build';
    default: {
      const _exhaustive: never = view;
      return _exhaustive;
    }
  }
}

/** Read the current view from `window.location.hash`. */
export function readView(): ViewState {
  return parseHash(window.location.hash);
}

/** The current raw hash, for comparing a serialized view against the URL. */
export function currentHash(): string {
  return window.location.hash;
}

/**
 * Build the next URL for `view`. The hash is the app's only view-state channel,
 * so we normalize to a hash-only canonical URL: any stray query string (a shared
 * link's `?utm=…`, a dev-server param) is dropped rather than carried forward.
 */
function nextUrl(view: ViewState): string {
  const url = new URL(window.location.href);
  url.hash = serializeHash(view);
  url.search = '';
  return url.toString();
}

/** Write without adding a history entry (in-place correction, load-restore). */
export function replaceView(view: ViewState): void {
  window.history.replaceState({}, '', nextUrl(view));
}

/** Write and add a history entry (deliberate user navigation → Back works). */
export function pushView(view: ViewState): void {
  window.history.pushState({}, '', nextUrl(view));
}
