import { act, renderHook } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import {
  parseServerMode,
  resolveTransitVariant,
  travelFieldKey,
  useTravelTime,
  type TravelTimeEntry,
} from './useTravelTime';

describe('useTravelTime', () => {
  it('creates backend field keys from mode and destination slug', () => {
    expect(
      travelFieldKey({
        mode: 'transit',
        slug: 'kings-cross',
        label: 'Kings Cross',
        timeRange: [0, 45],
        useBest: true,
      })
    ).toBe('tt_transit_kings-cross');
  });

  it('adds, updates, toggles, and removes travel-time entries', () => {
    const { result } = renderHook(() => useTravelTime());

    act(() => result.current.handleAddEntry('transit'));
    expect(result.current.entries).toEqual([
      {
        mode: 'transit',
        slug: '',
        label: '',
        timeRange: null,
        useBest: false,
        noChange: false,
        oneChange: false,
        noBuses: false,
      },
    ]);
    expect(result.current.activeEntries).toEqual([]);

    act(() => result.current.handleSetDestination(0, 'bank', 'Bank'));
    expect(result.current.entries[0]).toMatchObject({
      slug: 'bank',
      label: 'Bank',
      timeRange: [0, 90],
    });
    expect(result.current.activeEntries).toHaveLength(1);

    act(() => result.current.handleTimeRangeChange(0, [10, 35]));
    expect(result.current.entries[0].timeRange).toEqual([10, 35]);

    act(() => result.current.handleToggleBest(0));
    expect(result.current.entries[0].useBest).toBe(true);

    act(() => result.current.handleRemoveEntry(0));
    expect(result.current.entries).toEqual([]);
  });

  it('replaces entries wholesale for AI-generated filters', () => {
    const initial: TravelTimeEntry = {
      mode: 'walking',
      slug: 'old',
      label: 'Old',
      timeRange: [0, 20],
      useBest: false,
    };
    const replacement: TravelTimeEntry = {
      mode: 'car',
      slug: 'new',
      label: 'New',
      timeRange: [5, 30],
      useBest: false,
    };
    const { result } = renderHook(() => useTravelTime({ entries: [initial] }));

    act(() => result.current.handleSetEntries([replacement]));

    expect(result.current.entries).toEqual([replacement]);
    expect(result.current.activeEntries).toEqual([replacement]);
  });

  it('deduplicates initial and replacement entries using the tightest range', () => {
    const wide: TravelTimeEntry = {
      mode: 'transit',
      slug: 'bank-tube-station',
      label: 'Bank',
      timeRange: [0, 60],
      useBest: false,
    };
    const tight: TravelTimeEntry = {
      mode: 'transit',
      slug: 'bank-tube-station',
      label: 'Bank',
      timeRange: [10, 45],
      useBest: false,
    };
    const replacement: TravelTimeEntry = {
      mode: 'transit',
      slug: 'bank-tube-station',
      label: 'Bank',
      timeRange: [20, 40],
      useBest: true,
    };
    const { result } = renderHook(() => useTravelTime({ entries: [wide, tight] }));

    expect(result.current.entries).toEqual([
      {
        mode: 'transit',
        slug: 'bank-tube-station',
        label: 'Bank',
        timeRange: [10, 45],
        useBest: false,
      },
    ]);

    act(() => result.current.handleSetEntries([wide, replacement]));

    expect(result.current.entries).toEqual([
      {
        mode: 'transit',
        slug: 'bank-tube-station',
        label: 'Bank',
        timeRange: [20, 40],
        useBest: true,
      },
    ]);
  });

  it('toggles noChange and noBuses independently', () => {
    const { result } = renderHook(() => useTravelTime());
    act(() => result.current.handleAddEntry('transit'));
    act(() => result.current.handleSetDestination(0, 'bank', 'Bank'));

    expect(result.current.entries[0]).toMatchObject({ noChange: false, noBuses: false });

    act(() => result.current.handleToggleNoChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: true, noBuses: false });

    act(() => result.current.handleToggleNoBuses(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: true, noBuses: true });

    act(() => result.current.handleToggleNoChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: false, noBuses: true });
  });

  it('keeps noChange and oneChange mutually exclusive', () => {
    const { result } = renderHook(() => useTravelTime());
    act(() => result.current.handleAddEntry('transit'));
    act(() => result.current.handleSetDestination(0, 'bank', 'Bank'));

    act(() => result.current.handleToggleNoChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: true, oneChange: false });

    // Enabling oneChange clears noChange...
    act(() => result.current.handleToggleOneChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: false, oneChange: true });

    // ...and vice versa.
    act(() => result.current.handleToggleNoChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: true, oneChange: false });

    // Both can be off (no restriction): toggling the active one off leaves neither set.
    act(() => result.current.handleToggleNoChange(0));
    expect(result.current.entries[0]).toMatchObject({ noChange: false, oneChange: false });
  });
});

describe('resolveTransitVariant', () => {
  const base: TravelTimeEntry = {
    mode: 'transit',
    slug: 'bank',
    label: 'Bank',
    timeRange: [0, 90],
    useBest: false,
  };

  it('passes non-transit modes through unchanged', () => {
    expect(resolveTransitVariant({ ...base, mode: 'car' })).toBe('car');
    expect(resolveTransitVariant({ ...base, mode: 'bicycle' })).toBe('bicycle');
    expect(resolveTransitVariant({ ...base, mode: 'walking' })).toBe('walking');
  });

  it('maps transit toggle combinations to the right variant string', () => {
    expect(resolveTransitVariant(base)).toBe('transit');
    expect(resolveTransitVariant({ ...base, noChange: true })).toBe('transit-no-change');
    expect(resolveTransitVariant({ ...base, oneChange: true })).toBe('transit-one-change');
    expect(resolveTransitVariant({ ...base, noBuses: true })).toBe('transit-no-bus');
    expect(resolveTransitVariant({ ...base, noChange: true, noBuses: true })).toBe(
      'transit-no-change-no-bus'
    );
    expect(resolveTransitVariant({ ...base, oneChange: true, noBuses: true })).toBe(
      'transit-one-change-no-bus'
    );
  });

  it('lets noChange win if both change flags are somehow set', () => {
    expect(resolveTransitVariant({ ...base, noChange: true, oneChange: true })).toBe(
      'transit-no-change'
    );
  });

  it('treats undefined flags as false', () => {
    expect(resolveTransitVariant({ ...base, noChange: undefined, noBuses: undefined })).toBe(
      'transit'
    );
  });
});

describe('parseServerMode', () => {
  it('round-trips the six toggle-reachable transit variants', () => {
    expect(parseServerMode('transit')).toEqual({
      mode: 'transit',
      noChange: false,
      oneChange: false,
      noBuses: false,
    });
    expect(parseServerMode('transit-no-bus')).toEqual({
      mode: 'transit',
      noChange: false,
      oneChange: false,
      noBuses: true,
    });
    expect(parseServerMode('transit-no-change')).toEqual({
      mode: 'transit',
      noChange: true,
      oneChange: false,
      noBuses: false,
    });
    expect(parseServerMode('transit-no-change-no-bus')).toEqual({
      mode: 'transit',
      noChange: true,
      oneChange: false,
      noBuses: true,
    });
    expect(parseServerMode('transit-one-change')).toEqual({
      mode: 'transit',
      noChange: false,
      oneChange: true,
      noBuses: false,
    });
    expect(parseServerMode('transit-one-change-no-bus')).toEqual({
      mode: 'transit',
      noChange: false,
      oneChange: true,
      noBuses: true,
    });
  });

  it('parses non-transit base modes', () => {
    expect(parseServerMode('car')).toEqual({
      mode: 'car',
      noChange: false,
      oneChange: false,
      noBuses: false,
    });
    expect(parseServerMode('bicycle')).toEqual({
      mode: 'bicycle',
      noChange: false,
      oneChange: false,
      noBuses: false,
    });
    expect(parseServerMode('walking')).toEqual({
      mode: 'walking',
      noChange: false,
      oneChange: false,
      noBuses: false,
    });
  });

  it('returns null for variants the UI cannot represent (no silent broadening)', () => {
    expect(parseServerMode('unknown-mode')).toBeNull();
    expect(parseServerMode('transit-two-change')).toBeNull();
  });

  it('travelFieldKey uses the resolved variant', () => {
    expect(
      travelFieldKey({
        mode: 'transit',
        slug: 'bank',
        label: 'Bank',
        timeRange: [0, 90],
        useBest: false,
        noChange: true,
        noBuses: true,
      })
    ).toBe('tt_transit-no-change-no-bus_bank');
  });
});
