import { describe, it, expect, beforeEach } from 'vitest';
import {
  parseHash,
  serializeHash,
  readView,
  replaceView,
  pushView,
  type ViewState,
} from './url-hash';

/** One value per `ViewState` variant — the round-trip and serialize coverage set. */
const ALL_VIEWS: ViewState[] = [
  { kind: 'snippets' },
  { kind: 'snippet', snippetId: 'abc123' },
  { kind: 'datasets' },
  { kind: 'dataset', datasetId: 7 },
  { kind: 'dataset-new' },
  { kind: 'dataset-build', datasetId: 42 },
  { kind: 'build' },
];

describe('serializeHash', () => {
  it('produces the spec grammar form for each variant', () => {
    expect(serializeHash({ kind: 'snippets' })).toBe('');
    expect(serializeHash({ kind: 'snippet', snippetId: 'abc123' })).toBe('#snippet-abc123');
    expect(serializeHash({ kind: 'datasets' })).toBe('#datasets');
    expect(serializeHash({ kind: 'dataset', datasetId: 7 })).toBe('#datasets/dataset-7');
    expect(serializeHash({ kind: 'dataset-new' })).toBe('#datasets/new');
    expect(serializeHash({ kind: 'dataset-build', datasetId: 42 })).toBe(
      '#datasets/dataset-42/build',
    );
    expect(serializeHash({ kind: 'build' })).toBe('#build');
  });
});

describe('parseHash', () => {
  it('parses each grammar row to the right ViewState', () => {
    expect(parseHash('')).toEqual({ kind: 'snippets' });
    expect(parseHash('#snippet-abc123')).toEqual({ kind: 'snippet', snippetId: 'abc123' });
    expect(parseHash('#datasets')).toEqual({ kind: 'datasets' });
    expect(parseHash('#datasets/dataset-7')).toEqual({ kind: 'dataset', datasetId: 7 });
    expect(parseHash('#datasets/new')).toEqual({ kind: 'dataset-new' });
    expect(parseHash('#datasets/dataset-42/build')).toEqual({
      kind: 'dataset-build',
      datasetId: 42,
    });
    expect(parseHash('#build')).toEqual({ kind: 'build' });
  });

  it('tolerates a missing leading "#"', () => {
    expect(parseHash('datasets')).toEqual({ kind: 'datasets' });
    expect(parseHash('snippet-x')).toEqual({ kind: 'snippet', snippetId: 'x' });
  });

  it('treats a snippet id as an opaque string (slashes, dashes, etc.)', () => {
    expect(parseHash('#snippet-a-b-c')).toEqual({ kind: 'snippet', snippetId: 'a-b-c' });
    expect(parseHash('#snippet-2026-06-07')).toEqual({ kind: 'snippet', snippetId: '2026-06-07' });
  });

  it('falls back to the default snippets view on malformed / unknown hashes', () => {
    const fallback: ViewState = { kind: 'snippets' };
    expect(parseHash('#snippet-')).toEqual(fallback); // empty snippet id
    expect(parseHash('#datasets/dataset-abc')).toEqual(fallback); // non-numeric dataset id
    expect(parseHash('#datasets/dataset-')).toEqual(fallback); // empty dataset id
    expect(parseHash('#datasets/dataset-7/extra')).toEqual(fallback); // unknown trailing segment
    expect(parseHash('#datasets/bogus')).toEqual(fallback); // unknown datasets sub-route
    expect(parseHash('#garbage')).toEqual(fallback); // unknown top-level
  });

  it('never throws for arbitrary input', () => {
    for (const raw of ['#', '#/', '#//', '#datasets//', '#snippet', '###']) {
      expect(() => parseHash(raw)).not.toThrow();
      expect(parseHash(raw).kind).toBeDefined();
    }
  });
});

describe('round-trip identity', () => {
  it('parseHash(serializeHash(v)) deep-equals v for every variant', () => {
    for (const v of ALL_VIEWS) {
      expect(parseHash(serializeHash(v))).toEqual(v);
    }
  });
});

describe('readView / replaceView / pushView (window + history)', () => {
  beforeEach(() => {
    // Reset to a clean, hash-less location before each case.
    window.history.replaceState({}, '', '/');
  });

  it('readView reflects the current location.hash', () => {
    window.location.hash = '#datasets/dataset-7';
    expect(readView()).toEqual({ kind: 'dataset', datasetId: 7 });
  });

  it('readView returns the default for an empty hash', () => {
    expect(readView()).toEqual({ kind: 'snippets' });
  });

  it('replaceView writes the hash without growing history', () => {
    const before = window.history.length;
    replaceView({ kind: 'snippet', snippetId: 'xyz' });
    expect(window.location.hash).toBe('#snippet-xyz');
    expect(readView()).toEqual({ kind: 'snippet', snippetId: 'xyz' });
    expect(window.history.length).toBe(before);
  });

  it('replaceView clears the hash for the default view', () => {
    window.location.hash = '#datasets';
    replaceView({ kind: 'snippets' });
    expect(window.location.hash).toBe('');
    expect(readView()).toEqual({ kind: 'snippets' });
  });

  it('pushView writes the hash and adds a history entry', () => {
    const before = window.history.length;
    pushView({ kind: 'dataset-build', datasetId: 42 });
    expect(window.location.hash).toBe('#datasets/dataset-42/build');
    expect(readView()).toEqual({ kind: 'dataset-build', datasetId: 42 });
    expect(window.history.length).toBe(before + 1);
  });

  it('round-trips through the real history API for every variant', () => {
    for (const v of ALL_VIEWS) {
      replaceView(v);
      expect(readView()).toEqual(v);
    }
  });
});
