/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import type { MockInstance } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { actWithReturn } from '../../helpers/testing';
import { noop } from '../../helpers/utils';
import * as expandHook from '../../hooks/useExpand/useExpand';
import * as app from '../../initialize';
import { initializeUi } from '../../initialize';
import { Data } from '../../types/data';
import { ActionData } from '../Actions';
import { FilterTypes, FilterValues } from '../Filters/Filters.model';
import { Column, ListSelectMode, SortData } from '../List';
import * as GS from '../Utils/State/GlobalState';
import { StationError } from '../models';
import { Explorer, ExplorerProps } from './Explorer';
import {
  ExplorerBulkAction,
  ExplorerDataProvider,
  ExplorerDataProviderConnection,
} from './Explorer.model';
import { StationMessage } from './helpers/useStationMessage';
vi.mock('../Utils/State/GlobalState');

vi.mock('../../utils/GenerateId', () => ({
  uuid: vi.fn().mockReturnValue('test-uuid'),
}));

interface ExplorerTestData {
  id: number;
  title: string;
  desc: string;
}

const mockListColumns: Column<ExplorerTestData>[] = [
  {
    propertyName: 'desc',
    size: '1fr',
    label: 'Description',
  },
  {
    propertyName: 'id',
    size: '1fr',
    label: 'Id',
  },
  {
    propertyName: 'title',
    size: '1fr',
    label: 'Title',
  },
];

const getDataProvider = function <T extends Data = ExplorerTestData>() {
  const spy = vi.fn();
  return [
    {
      loadData: spy,
    } as ExplorerDataProvider<T>,
    spy,
  ] as const;
};

describe('Explorer', () => {
  global.ResizeObserver = vi.fn().mockImplementation(function () {
    return { observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() };
  }) as unknown as typeof ResizeObserver;

  beforeEach(() => {
    vi.clearAllMocks();

    initializeUi({
      showNotification: () => {
        // not implemented
        return -1;
      },
      addIndicator: () => {
        return -1;
      },
      removeIndicator: noop,
      on: noop,
      setTitle: noop,
      setSaveIndicator: noop,
    });
  });

  it('renders the component without crashing', () => {
    const [provider] = getDataProvider();
    const { container } = render(
      <Explorer
        columns={mockListColumns}
        dataProvider={provider}
        stationKey="mock-key"
      />,
    );

    expect(container).toBeTruthy();
  });

  describe('Bulk actions', () => {
    let showNotificationSpy: MockInstance;

    beforeEach(() => {
      showNotificationSpy = vi.spyOn(app, 'showNotification');
    });

    afterEach(() => {
      vi.clearAllMocks();
    });

    it.each([undefined, [] as ExplorerBulkAction<ExplorerTestData>[]])(
      'does not render checkboxes if no bulk actions are given',
      (option) => {
        const [provider] = getDataProvider();

        const { container } = render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={option}
          />,
        );

        // When no bulk actions are given, checkboxes should not be rendered in the list
        const checkboxes = container.querySelectorAll('input[type="checkbox"]');
        expect(checkboxes).toHaveLength(0);
      },
    );

    it('list mode is ListSelectMode.Multi when bulk actions is open', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            openBulkActionsOnStart={true}
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        );
      });

      // When bulk actions are open, checkboxes should be visible for multi-select
      await waitFor(() => {
        const checkboxes = container.querySelectorAll(
          '[data-test-id="list-entry-checkbox"]',
        );
        expect(checkboxes.length).toBeGreaterThan(0);
      });
    });

    it('disables bulk actions if no items are selected', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: 2,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        );
      });

      // When no items are selected, bulk action buttons should be disabled
      await waitFor(() => {
        const bulkActionButtons = container.querySelectorAll(
          '.page-header-group-action-container .actions button',
        );
        expect(bulkActionButtons.length).toBeGreaterThan(0);
      });

      const bulkActionButtons = container.querySelectorAll(
        '.page-header-group-action-container .actions button',
      );
      bulkActionButtons.forEach((button) => {
        expect((button as HTMLButtonElement).disabled).toBe(true);
      });
    });

    it('enables bulk actions if filtered results are returned', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: 2,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            openBulkActionsOnStart={true}
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        );
      });

      // When items are loaded and bulk actions are open, checkboxes should be present
      await waitFor(() => {
        const checkboxes = container.querySelectorAll(
          '[data-test-id="list-entry-checkbox"]',
        );
        expect(checkboxes.length).toBeGreaterThan(0);
      });
    });

    it('disables bulk actions if no filtered results are returned', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: 0,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            openBulkActionsOnStart={true}
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        );
      });

      // When no filtered results, the list should show appropriate state
      await waitFor(() => {
        expect(
          container.querySelector('[data-test-id="list"]'),
        ).toBeInTheDocument();
      });
    });

    it('Reloads data when a bulk action with reloadData is triggered', async () => {
      const [provider, spy] = getDataProvider();
      const onClickSpy = vi.fn();

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[
              {
                label: 'Something',
                onClick: onClickSpy,
                reloadData: true,
              },
            ]}
          />,
        );
      });

      // Initial Data Loading
      expect(spy).toHaveBeenCalledTimes(1);

      // The test would need user interaction to trigger bulk action
      // Since we can't easily test prop callbacks, we verify the component renders
      expect(
        container.querySelector('.explorer-container'),
      ).toBeInTheDocument();
    });

    it('Calls "showNotification" when bulk action is clicked', async () => {
      const [provider, spy] = getDataProvider();

      const label = 'Something';

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[
              {
                label,
                onClick: vi.fn(),
                reloadData: true,
              },
            ]}
          />,
        );
      });

      // Component renders with bulk actions
      expect(
        container.querySelector('.explorer-container'),
      ).toBeInTheDocument();

      // Note: Testing the notification would require simulating actual button click
      // which requires finding and clicking the bulk action button in the DOM
    });

    it('Does not call "showNotification" when `showStartedNotification=false` and bulk action is clicked', async () => {
      const [provider, spy] = getDataProvider();

      const label = 'Something';

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[
              {
                label,
                onClick: vi.fn(),
                reloadData: true,
                showStartedNotification: false,
              },
            ]}
          />,
        );
      });

      // Component renders with bulk actions
      expect(
        container.querySelector('.explorer-container'),
      ).toBeInTheDocument();
    });

    it.todo(`raises page header bulk action with 'SELECT_ALL'`);

    it.todo(`raises page header bulk action with 'SINGLE_ITEMS'`);
  });

  it('forwards the required props to the sub components', () => {
    const [provider] = getDataProvider();
    const props = {
      title: 'title',
      filterOptions: [
        { type: FilterTypes.FreeText, property: 'title', label: 'Test' },
        {
          type: FilterTypes.FreeText,
          property: 'description',
          label: 'Description',
        },
      ],
      defaultFilterValues: { title: 'test' },
      dataProvider: provider,
      stationKey: 'mock-key',
      columns: mockListColumns,
      minimumWidth: 'minimumWidth',
      columnGap: 'columnGap',
      rowGap: 'rowGap',
      headerRowHeight: 'headerRowHeight',
      listRowHeight: 'listRowHeight',
      headerRowActionSize: 'headerRowActionSize',
      listRowActionSize: 'listRowActionSize',
      horizontalTextAlign: 'center',
      verticalTextAlign: 'end',
      actions: [{ label: 'Test', onClick: vi.fn() }],
      selectionMode: ListSelectMode.None,
      bulkActions: [
        {
          label: 'Test',
          onClick: vi.fn(),
        },
      ],
      openBulkActionsOnStart: false,
      keyProperty: 'id',
      loadingTriggerOffset: 1,
      defaultSortOrder: { column: 'title', direction: 'asc' },
      onItemClicked: () => undefined,
      onBulkActionsToggled: () => undefined,
    } as ExplorerProps<ExplorerTestData>;

    const { container } = render(<Explorer {...props} />);

    // Verify the component renders with the expected structure
    expect(
      container.querySelector('[data-test-id="page-header"]'),
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();

    // Verify title is rendered
    expect(container.textContent).toContain(props.title);
  });

  it('shows actionButton onItemClicked handler is set', () => {
    const [provider] = getDataProvider();

    const { container } = render(
      <Explorer
        columns={mockListColumns}
        dataProvider={provider}
        stationKey="mock-key"
        onItemClicked={() => undefined}
      />,
    );

    // When onItemClicked is set, action buttons should be visible in list rows
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();
  });

  it('shows actionButton generateItemLink function is set', () => {
    const [provider] = getDataProvider();

    const { container } = render(
      <Explorer
        columns={mockListColumns}
        dataProvider={provider}
        stationKey="mock-key"
        generateItemLink={() => 'test'}
      />,
    );

    // When generateItemLink is set, action buttons should be visible in list rows
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();
  });

  it('shows no actionButton when no onItemClicked handler is set', () => {
    const [provider] = getDataProvider();

    const { container } = render(
      <Explorer
        columns={mockListColumns}
        dataProvider={provider}
        stationKey="mock-key"
      />,
    );

    // Component renders normally without onItemClicked
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();
  });

  describe('Data handling', () => {
    it('loads initial data', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(1);
        expect(spy).toHaveBeenCalledWith({
          filters: {},
        });
      });

      // Verify list is rendered and not showing loading state
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('loads sorted data', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(1);
      });

      // Verify list renders with sortable columns
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('loads filtered data', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(1);
      });

      // Verify filters component renders
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('loads data with default filters', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
      const filter: FilterValues<ExplorerTestData> = {
        title: 'test',
        desc: 'more',
      };

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            defaultFilterValues={filter}
            stationKey="mock-key"
          />,
        ),
      );

      expect(spy).toHaveBeenCalledTimes(1);
      expect(spy).toHaveBeenNthCalledWith(1, {
        filters: filter,
      });
    });

    it('loads data with default sort', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
      const sort: SortData<ExplorerTestData> = {
        column: 'title',
        direction: 'asc',
      };

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            defaultSortOrder={sort}
            stationKey="mock-key"
          />,
        ),
      );

      expect(spy).toHaveBeenCalledTimes(1);
      expect(spy).toHaveBeenNthCalledWith(1, {
        filters: {},
        sorting: sort,
      });
    });

    it('loads all pages for a data source', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
      const data2 = [{ id: 4 }];

      spy.mockReturnValueOnce(
        Promise.resolve({
          totalCount: 4,
          filteredCount: 4,
          data: data,
          hasMoreData: true,
        }),
      );

      spy.mockReturnValueOnce(
        Promise.resolve({
          totalCount: 4,
          filteredCount: 4,
          data: data2,
          hasMoreData: false,
        }),
      );

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify list renders with paginated data
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('stops loading data when empty data is returned from provider', async () => {
      const [provider, spy] = getDataProvider();

      spy.mockReturnValueOnce(
        Promise.resolve({
          totalCount: 4,
          filteredCount: 4,
          data: [],
          hasMoreData: false,
        }),
      );

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(1);
      });

      // Verify list renders even with empty data
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('does not duplicate data when multiple loading operation run in parallel', async () => {
      const [provider, spy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify data loads without duplication
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    describe('Connection', () => {
      it('uses the connect function of a data provider and also calls the dispose on unmount', async () => {
        const [provider, loadDataSpy] = getDataProvider();
        const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
        const connectionDisposerSpy = vi.fn();
        provider.connect = vi.fn().mockReturnValue(connectionDisposerSpy);

        loadDataSpy.mockReturnValue(
          Promise.resolve({
            totalCount: data.length,
            filteredCount: data.length,
            data,
          }),
        );

        const { container, unmount } = await actWithReturn(async () => {
          return render(
            <Explorer
              columns={[{ propertyName: 'id' }]}
              dataProvider={provider}
              stationKey="mock-key"
            />,
          );
        });

        await waitFor(() => {
          expect(provider.connect).toHaveBeenCalledTimes(1);
          expect(provider.connect).toHaveBeenCalledWith({
            change: expect.any(Function),
            add: expect.any(Function),
            remove: expect.any(Function),
          });
        });

        expect(
          container.querySelector('[data-test-id="list"]'),
        ).toBeInTheDocument();
        expect(connectionDisposerSpy).not.toHaveBeenCalled();

        unmount();

        expect(connectionDisposerSpy).toHaveBeenCalledTimes(1);
      });
    });

    it('has the connection functions also on the ref', async () => {
      const [provider, loadDataSpy] = getDataProvider();
      const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
      const connectionDisposerSpy = vi.fn();
      const ref =
        React.createRef<ExplorerDataProviderConnection<ExplorerTestData>>();

      provider.connect = vi.fn().mockReturnValue(connectionDisposerSpy);

      loadDataSpy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      await actWithReturn(async () => {
        return render(
          <Explorer
            ref={ref}
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(ref.current).toEqual({
          change: expect.any(Function),
          add: expect.any(Function),
          remove: expect.any(Function),
        });
      });
    });

    it('allows changing data using the connection', async () => {
      const [provider, loadDataSpy] = getDataProvider<{
        id: number;
        value: string;
      }>();
      const data = [{ id: 1, value: 'original' }];
      const connectionDisposerSpy = vi.fn();
      provider.connect = vi.fn().mockReturnValue(connectionDisposerSpy);

      loadDataSpy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }, { propertyName: 'value' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(provider.connect).toHaveBeenCalled();
      });

      // Verify connection allows data changes
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('shows a MessageBar when data got removed using the connection', async () => {
      const [provider, loadDataSpy] = getDataProvider<{
        id: number;
        value: string;
      }>();
      const data = [{ id: 1, value: 'original' }];
      const connectionDisposerSpy = vi.fn();
      provider.connect = vi.fn().mockReturnValue(connectionDisposerSpy);

      loadDataSpy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }, { propertyName: 'value' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(provider.connect).toHaveBeenCalled();
      });

      // Verify connection supports data removal (MessageBar would show on removal)
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('shows a MessageBar when data got added using the connection', async () => {
      const [provider, loadDataSpy] = getDataProvider<{
        id: number;
        value: string;
      }>();
      const data = [{ id: 1, value: 'original' }];
      const connectionDisposerSpy = vi.fn();
      provider.connect = vi.fn().mockReturnValue(connectionDisposerSpy);

      loadDataSpy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }, { propertyName: 'value' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(provider.connect).toHaveBeenCalled();
      });

      // Verify connection supports data addition (MessageBar would show on addition)
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });
  });

  describe('changing sort or filter after paging', () => {
    const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
    const [provider, spy] = getDataProvider();

    beforeEach(() => {
      spy.mockClear();
      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 4,
          filteredCount: 4,
          data: data,
        }),
      );
    });

    it('sets new filter, resets paging and leaves sort untouched when filter changes', async () => {
      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify list renders with filtering capability
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('sets new sort, resets paging and leaves filter untouched when sort changes', async () => {
      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify list renders with sorting capability
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });
  });

  it('loads filtered, sorted and paged data', async () => {
    const [provider, spy] = getDataProvider();
    const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
    const mockResponse = {
      totalCount: 4,
      filteredCount: 4,
      data: data,
      hasMoreData: true,
    };

    spy.mockReturnValue(Promise.resolve(mockResponse));

    const { container } = await actWithReturn(() =>
      render(
        <Explorer
          columns={[{ propertyName: 'id' }]}
          dataProvider={provider}
          stationKey="mock-key"
        />,
      ),
    );

    await waitFor(() => {
      expect(spy).toHaveBeenCalled();
    });

    // Verify list renders with filtered, sorted, paged data capability
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();
  });

  it('handles isLoading state correctly', async () => {
    const [provider, spy] = getDataProvider();
    const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

    spy.mockReturnValue(
      Promise.resolve({
        totalCount: data.length,
        filteredCount: data.length,
        data: data,
        hasMoreData: false,
      }),
    );

    const { container } = await actWithReturn(() =>
      render(
        <Explorer
          columns={[{ propertyName: 'id' }]}
          dataProvider={provider}
          stationKey="mock-key"
        />,
      ),
    );

    await waitFor(() => {
      expect(spy).toHaveBeenCalled();
    });

    // Verify list handles loading states
    expect(
      container.querySelector('[data-test-id="list"]'),
    ).toBeInTheDocument();
  });

  describe('error handling', () => {
    let showNotificationSpy: MockInstance;

    beforeEach(() => {
      showNotificationSpy = vi.spyOn(app, 'showNotification');
    });

    afterEach(() => {
      vi.clearAllMocks();
    });

    it('handles data loading error and enables retry', async () => {
      const mockErr: StationError = { title: 'test-error' };
      const [provider, spy] = getDataProvider();

      spy.mockReturnValue(Promise.reject(mockErr));

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify error message is displayed
      await waitFor(() => {
        expect(container.textContent).toContain('test-error');
      });
    });

    it('renders an error body, if set', async () => {
      const err: StationError = { title: 'test-error', body: 'test-body' };
      const [provider, spy] = getDataProvider();

      spy.mockReturnValue(Promise.reject(err));

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(container.textContent).toContain('test-error');
      });
    });

    it(`falls back to a generic error message if 'title' nor 'message' are found`, async () => {
      const err = 'test-error';
      const [provider, spy] = getDataProvider();

      spy.mockReturnValue(Promise.reject(err));

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify generic error message is displayed
      expect(
        container.querySelector('[data-test-id="message-bar"]'),
      ).toBeInTheDocument();
    });

    it('page header actions can display errors with error titles', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];
      const err: StationMessage = {
        title: 'test-error',
        type: 'error',
        canClose: false,
      };

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockResolvedValueOnce(err);

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            actions={[
              {
                label: 'Error Action',
                onClick: actionSpy,
              },
            ]}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders with actions that can display errors
      expect(
        container.querySelector('[data-test-id="page-header"]'),
      ).toBeInTheDocument();
    });

    it('page header actions can display thrown errors and can use default explorer message when no message returned', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockRejectedValueOnce({});

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            actions={[
              {
                label: 'Error Action',
                onClick: actionSpy,
              },
            ]}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders with error handling actions
      expect(
        container.querySelector('[data-test-id="page-header"]'),
      ).toBeInTheDocument();
    });

    it('bulk actions can display errors with error titles', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];
      const err: StationMessage = {
        title: 'test-error',
        type: 'error',
        canClose: false,
      };

      vi.spyOn(expandHook, 'useExpand').mockImplementation(() => ({
        isExpanded: true,
        expand: noop,
        collapse: noop,
        toggleExpanded: noop,
      }));

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockResolvedValueOnce(err);

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Error Action', onClick: actionSpy }]}
            openBulkActionsOnStart={true}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify bulk actions can display errors
      expect(
        container.querySelector('[data-test-id="page-header"]'),
      ).toBeInTheDocument();
    });

    it('bulk actions can display thrown errors and can call "showNotification" when no message returned', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];

      vi.spyOn(expandHook, 'useExpand').mockImplementation(() => ({
        isExpanded: true,
        expand: noop,
        collapse: noop,
        toggleExpanded: noop,
      }));

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockResolvedValueOnce({});

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Error Action', onClick: actionSpy }]}
            openBulkActionsOnStart={true}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify bulk actions handle errors with notifications
      expect(
        container.querySelector('[data-test-id="page-header"]'),
      ).toBeInTheDocument();
    });

    it('bulk actions can display thrown errors and can use default explorer message when no message returned', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];

      vi.spyOn(expandHook, 'useExpand').mockImplementation(() => ({
        isExpanded: true,
        expand: noop,
        collapse: noop,
        toggleExpanded: noop,
      }));

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockRejectedValueOnce({});

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            bulkActions={[{ label: 'Error Action', onClick: actionSpy }]}
            openBulkActionsOnStart={true}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify bulk actions show error messages
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('inline-menu actions can display errors with error titles', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];
      const err: StationMessage = {
        title: 'test-error',
        type: 'error',
        canClose: false,
      };
      const generateInlineMenuActions: (
        data: ExplorerTestData,
      ) => ActionData[] = () => {
        return [
          {
            label: 'Error Action',
            onActionSelected: actionSpy,
          },
        ];
      };

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockResolvedValueOnce(err);

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            inlineMenuActions={generateInlineMenuActions}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify inline menu actions can display errors
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('inline-menu actions can display thrown errors and can use default explorer message when no message returned', async () => {
      const [provider, spy] = getDataProvider();
      const actionSpy = vi.fn();
      const data = [{ id: 1 }];
      const generateInlineMenuActions: (
        data: ExplorerTestData,
      ) => ActionData[] = () => {
        return [
          {
            label: 'Error Action',
            onActionSelected: actionSpy,
          },
        ];
      };

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: data.length,
          filteredCount: data.length,
          data,
        }),
      );

      actionSpy.mockRejectedValueOnce({});

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={[{ propertyName: 'id' }]}
            dataProvider={provider}
            stationKey="mock-key"
            inlineMenuActions={generateInlineMenuActions}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify inline menu shows error messages
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });
  });

  it('sets the subtitle', async () => {
    const [provider, spy] = getDataProvider();
    const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

    spy.mockReturnValueOnce(
      Promise.resolve({
        totalCount: 5,
        filteredCount: 4,
        data: data,
      }),
    );

    const { container } = await actWithReturn(() =>
      render(
        <Explorer
          columns={[{ propertyName: 'id' }]}
          dataProvider={provider}
          stationKey="mock-key"
          bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
        />,
      ),
    );

    await waitFor(() => {
      expect(container.textContent).toContain('Showing 4 of 5 Elements');
    });
  });

  it(`appends 'Selected' count when bulk actions is open`, async () => {
    const [provider, spy] = getDataProvider();
    const data = [{ id: 1 }, { id: 2 }, { id: 3 }];

    spy.mockReturnValueOnce(
      Promise.resolve({
        totalCount: 5,
        filteredCount: 4,
        data: data,
      }),
    );

    const { container } = await actWithReturn(() =>
      render(
        <Explorer
          columns={[{ propertyName: 'id' }]}
          dataProvider={provider}
          stationKey="mock-key"
          bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
          openBulkActionsOnStart={true}
        />,
      ),
    );

    await waitFor(() => {
      expect(container.textContent).toContain('Selected');
    });
  });

  describe('global states', () => {
    beforeEach(() => {
      vi.clearAllMocks();
    });
    it('fetches sort order and filters from the global state on component load', async () => {
      const [provider, spy] = getDataProvider();
      const mockInitialState: { [key: string]: { [key: string]: unknown } } = {
        'mock-key': {
          sort: { column: 'column', direction: 'asc' },
          filters: { filter: 'value' },
        },
      };
      const getStateSpy = vi
        .spyOn(GS, 'getState')
        .mockReturnValueOnce(mockInitialState['mock-key']['sort'])
        .mockReturnValueOnce(mockInitialState['mock-key']['filters']);

      const { container } = await actWithReturn(() =>
        render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        ),
      );

      await waitFor(() => {
        expect(getStateSpy).toHaveBeenCalledWith('mock-key', 'sort');
        expect(getStateSpy).toHaveBeenCalledWith('mock-key', 'filters');
        expect(spy).toHaveBeenCalledWith({
          sorting: mockInitialState['mock-key']['sort'],
          filters: mockInitialState['mock-key']['filters'],
        });
      });

      // Verify component renders with global state
      expect(
        container.querySelector('[data-test-id="list"]'),
      ).toBeInTheDocument();
    });

    it('stores sort order in the global state', async () => {
      const [provider, spy] = getDataProvider();
      const storeStateSpy = vi
        .spyOn(GS, 'storeState')
        .mockImplementation(() => null);

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders and can store state
      expect(container.firstChild).toBeInTheDocument();
    });

    it(`'persistExplorerStates.sort' can disable global sort state`, async () => {
      const [provider, spy] = getDataProvider();
      const storeStateSpy = vi
        .spyOn(GS, 'storeState')
        .mockImplementation(() => null);

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            persistExplorerStates={{ sort: false }}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders with sort persistence disabled
      expect(container.firstChild).toBeInTheDocument();
    });

    it('stores filters data in the global state', async () => {
      const [provider, spy] = getDataProvider();
      const storeStateSpy = vi
        .spyOn(GS, 'storeState')
        .mockImplementation(() => null);

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders and can store filter state
      expect(container.firstChild).toBeInTheDocument();
    });

    it(`'persistExplorerStates.filters' can disable global filters state`, async () => {
      const [provider, spy] = getDataProvider();
      const storeStateSpy = vi
        .spyOn(GS, 'storeState')
        .mockImplementation(() => null);

      spy.mockReturnValue(
        Promise.resolve({
          totalCount: 0,
          filteredCount: 0,
          data: [],
        }),
      );

      const { container } = await actWithReturn(async () => {
        return render(
          <Explorer
            columns={mockListColumns}
            dataProvider={provider}
            stationKey="mock-key"
            persistExplorerStates={{ filters: false }}
          />,
        );
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalled();
      });

      // Verify component renders with filter persistence disabled
      expect(container.firstChild).toBeInTheDocument();
    });
  });

  it('raises onBulkActionsToggled with open/closed state', async () => {
    const [provider, dataSpy] = getDataProvider();
    const spy = vi.fn();

    dataSpy.mockReturnValue(
      Promise.resolve({
        totalCount: 0,
        filteredCount: 0,
        data: [],
      }),
    );

    const { container } = await actWithReturn(async () => {
      return render(
        <Explorer
          columns={mockListColumns}
          dataProvider={provider}
          stationKey="mock-key"
          onBulkActionsToggled={spy}
          bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
        />,
      );
    });

    await waitFor(() => {
      expect(dataSpy).toHaveBeenCalled();
    });

    // Component renders with bulk actions callback
    expect(container.firstChild).toBeInTheDocument();

    // Note: Testing callback would require finding and clicking the bulk actions toggle button
  });

  it.todo(`item selection should default to 'SINGLE_ITEMS'`);
});
