import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useSourceStore, getFilterValues } from './store'
import type {
  AllExternalSources,
  RasterSource,
  Source,
  TilesetSource,
} from './types'
import {
  FilterType,
  TileFormat,
  type AddFilterOptions,
  type Filters,
} from '@carto/api-client'

describe('SourceStore', () => {
  beforeEach(() => {
    // Reset the store before each test
    useSourceStore.setState({
      sources: {},
      filters: {},
    })
  })

  describe('setSource', () => {
    it('should set a source with valid data', () => {
      const sourceData: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('test-id', sourceData)

      const source = useSourceStore.getState().sources['test-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('table')
      expect(source?.data).toBe('test_table')
    })

    it('should no longer remove source when undefined is passed (breaking change)', () => {
      const sourceData: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      // First set a source
      useSourceStore.getState().setSource('test-id', sourceData)
      expect(useSourceStore.getState().sources['test-id']).toBeDefined()

      // Setting undefined should now throw a TypeScript error and not be allowed
      // This test demonstrates the breaking change
      // useSourceStore.getState().setSource('test-id', undefined) // This would now be a TS error

      // Source should still exist since we can't pass undefined anymore
      expect(useSourceStore.getState().sources['test-id']).toBeDefined()
    })

    it('should update an existing source', () => {
      const sourceData: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('test-id', sourceData)

      const updateData: Partial<Source> = {
        data: 'updated_table',
      }

      useSourceStore.getState().setSource('test-id', updateData)

      const source = useSourceStore.getState().sources['test-id']
      expect(source?.data).toBe('updated_table')
      expect(source?.type).toBe('table') // Should preserve original type
    })
  })

  describe('removeSource', () => {
    it('should explicitly remove a source', () => {
      const sourceData: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('test-id', sourceData)
      expect(useSourceStore.getState().sources['test-id']).toBeDefined()

      useSourceStore.getState().removeSource('test-id')
      expect(useSourceStore.getState().sources['test-id']).toBeUndefined()
    })
  })

  describe('setFilters', () => {
    it('should set filters with valid data', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)

      const sourceFilters = useSourceStore.getState().filters['test-id']
      expect(sourceFilters).toBeDefined()
      expect(sourceFilters?.category?.in?.values).toEqual(['A', 'B'])
    })

    it('should no longer remove filters when empty array is passed (breaking change)', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      // First set filters
      useSourceStore.getState().setFilters('test-id', filters)
      expect(useSourceStore.getState().filters['test-id']).toBeDefined()
      expect(
        Object.keys(useSourceStore.getState().filters['test-id']!),
      ).toHaveLength(1)

      // Setting empty array should now preserve existing filters (no automatic removal)
      useSourceStore.getState().setFilters('test-id', [])
      expect(useSourceStore.getState().filters['test-id']).toBeDefined()
      expect(
        Object.keys(useSourceStore.getState().filters['test-id']!),
      ).toHaveLength(1)
    })

    it('should add new filters while preserving existing ones', () => {
      const initialFilters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      const additionalFilters: AddFilterOptions[] = [
        {
          column: 'status',
          type: FilterType.IN,
          values: ['active'],
        },
      ]

      // Set initial filters
      useSourceStore.getState().setFilters('test-id', initialFilters)
      expect(
        Object.keys(useSourceStore.getState().filters['test-id']!),
      ).toHaveLength(1)

      // Add more filters
      useSourceStore.getState().setFilters('test-id', additionalFilters)

      const sourceFilters = useSourceStore.getState().filters['test-id']
      expect(Object.keys(sourceFilters!)).toHaveLength(2)
      expect(sourceFilters?.category?.in?.values).toEqual(['A', 'B'])
      expect(sourceFilters?.status?.in?.values).toEqual(['active'])
    })
  })

  describe('removeFilters', () => {
    it('should explicitly remove filters', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)
      expect(useSourceStore.getState().filters['test-id']).toBeDefined()

      useSourceStore.getState().removeFilters('test-id', 'category')
      expect(useSourceStore.getState().filters['test-id']).toEqual({})
    })

    it('should remove filters with owner parameter', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
          owner: 'widget-1',
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)
      expect(useSourceStore.getState().filters['test-id']).toBeDefined()

      useSourceStore.getState().removeFilters('test-id', 'category', 'widget-1')
      expect(useSourceStore.getState().filters['test-id']).toEqual({})
    })

    it('should handle removing from non-existent source', () => {
      const filtersBefore = useSourceStore.getState().filters
      useSourceStore.getState().removeFilters('non-existent-id', 'category')
      const filtersAfter = useSourceStore.getState().filters

      // Should create empty filters object for non-existent source
      expect(filtersAfter['non-existent-id']).toEqual({})
      expect(Object.keys(filtersBefore).length).toBe(0)
    })

    it('should clear all filters when column is undefined', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
        {
          column: 'status',
          type: FilterType.IN,
          values: ['active'],
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)
      expect(
        Object.keys(useSourceStore.getState().filters['test-id']!),
      ).toHaveLength(2)

      useSourceStore.getState().removeFilters('test-id')
      expect(useSourceStore.getState().filters['test-id']).toEqual({})
    })
  })

  describe('invalidateSourcesCache', () => {
    it('should set cache control headers for external sources', () => {
      const querySource: Partial<Source> = {
        type: 'query',
        data: 'SELECT * FROM table',
      }

      useSourceStore.getState().setSource('query-id', querySource)

      useSourceStore
        .getState()
        .invalidateSourcesCache(['query-id'], { timeout: 0 })

      const source = useSourceStore.getState().sources['query-id']
      expect(source).toBeDefined()
      // Cache control is handled internally by the source
    })

    it('should not invalidate cache for geojson sources', () => {
      const geojsonSource: Partial<Source> = {
        type: 'geojson',
        data: [],
      }

      useSourceStore.getState().setSource('geojson-id', geojsonSource)

      useSourceStore
        .getState()
        .invalidateSourcesCache(['geojson-id'], { timeout: 0 })

      const source = useSourceStore.getState().sources['geojson-id']
      expect(source).toBeDefined()
    })

    it('should use custom maxAge parameter', () => {
      const tableSource: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('table-id', tableSource)

      useSourceStore.getState().invalidateSourcesCache(['table-id'], {
        timeout: 0,
        maxAge: 30,
      })

      const source = useSourceStore.getState().sources['table-id']
      expect(source).toBeDefined()
    })

    it('should handle multiple source IDs', () => {
      const querySource: Partial<Source> = {
        type: 'query',
        data: 'SELECT * FROM table',
      }
      const tableSource: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('query-id', querySource)
      useSourceStore.getState().setSource('table-id', tableSource)

      useSourceStore
        .getState()
        .invalidateSourcesCache(['query-id', 'table-id'], { timeout: 0 })

      const querySourceAfter = useSourceStore.getState().sources['query-id']
      const tableSourceAfter = useSourceStore.getState().sources['table-id']

      expect(querySourceAfter).toBeDefined()
      expect(tableSourceAfter).toBeDefined()
    })

    it('should handle empty array of source IDs', () => {
      useSourceStore.getState().invalidateSourcesCache([], { timeout: 0 })
      // Should not throw and complete successfully
      expect(true).toBe(true)
    })
  })

  describe('multiple filter operations', () => {
    it('should handle adding multiple filters of different types', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
        {
          column: 'price',
          type: FilterType.BETWEEN,
          values: [[10, 100]],
        },
        {
          column: 'status',
          type: FilterType.IN,
          values: ['active'],
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)

      const sourceFilters = useSourceStore.getState().filters['test-id']
      expect(Object.keys(sourceFilters!)).toHaveLength(3)
      expect(sourceFilters?.category?.in?.values).toEqual(['A', 'B'])
      expect(sourceFilters?.price?.between?.values).toEqual([[10, 100]])
      expect(sourceFilters?.status?.in?.values).toEqual(['active'])
    })

    it('should update existing filter when setting same column', () => {
      const initialFilters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      const updatedFilters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['C', 'D'],
        },
      ]

      useSourceStore.getState().setFilters('test-id', initialFilters)
      useSourceStore.getState().setFilters('test-id', updatedFilters)

      const sourceFilters = useSourceStore.getState().filters['test-id']
      expect(sourceFilters?.category?.in?.values).toEqual(['C', 'D'])
    })
  })

  describe('getFilterValues', () => {
    it('should return filter values for existing filter', () => {
      const filters = {
        category: {
          in: {
            values: ['A', 'B', 'C'],
          },
        },
      }

      const result = getFilterValues(filters, {
        column: 'category',
        type: FilterType.IN,
      })

      expect(result).toEqual(['A', 'B', 'C'])
    })

    it('should return empty array for non-existent column', () => {
      const filters = {
        category: {
          in: {
            values: ['A', 'B', 'C'],
          },
        },
      }

      const result = getFilterValues(filters, {
        column: 'non-existent',
        type: FilterType.IN,
      })

      expect(result).toEqual([])
    })

    it('should return empty array for non-existent filter type', () => {
      const filters = {
        category: {
          in: {
            values: ['A', 'B', 'C'],
          },
        },
      }

      const result = getFilterValues(filters, {
        column: 'category',
        type: FilterType.BETWEEN,
      })

      expect(result).toEqual([])
    })

    it('should return empty array when filters is undefined', () => {
      const result = getFilterValues(undefined, {
        column: 'category',
        type: FilterType.IN,
      })

      expect(result).toEqual([])
    })

    it('should handle BETWEEN filter type', () => {
      const filters = {
        price: {
          between: {
            values: [[10, 100]],
          },
        },
      }

      const result = getFilterValues(filters, {
        column: 'price',
        type: FilterType.BETWEEN,
      })

      expect(result).toEqual([[10, 100]])
    })

    it('should handle CLOSED_OPEN filter type', () => {
      const filters = {
        range: {
          closed_open: {
            values: [[0, 50]],
          },
        },
      }

      const result = getFilterValues(filters, {
        column: 'range',
        type: FilterType.CLOSED_OPEN,
      })

      expect(result).toEqual([[0, 50]])
    })
  })

  describe('setSource with different source types', () => {
    it('should create widget client for query source', () => {
      const querySource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
        apiBaseUrl: 'https://test.api.com',
      }

      useSourceStore.getState().setSource('query-id', querySource)

      const source = useSourceStore.getState().sources['query-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('query')
      expect(source?.widgets).toBeDefined()
    })

    it('should create widget client for table source', () => {
      const tableSource: Partial<Source<AllExternalSources>> = {
        type: 'table',
        data: 'test_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('table-id', tableSource)

      const source = useSourceStore.getState().sources['table-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('table')
      expect(source?.widgets).toBeDefined()
    })

    it('should create widget client for geojson source', () => {
      const geojsonSource: Partial<Source> = {
        type: 'geojson',
        data: [
          {
            type: 'Feature',
            geometry: { type: 'Point', coordinates: [0, 0] },
            properties: { name: 'Test' },
          },
        ],
      }

      useSourceStore.getState().setSource('geojson-id', geojsonSource)

      const source = useSourceStore.getState().sources['geojson-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('geojson')
      expect(source?.widgets).toBeDefined()
    })

    it('should handle tileset source without spatialDataBounds', () => {
      const tilesetSource: Partial<Source<AllExternalSources>> = {
        type: 'tileset',
        data: 'test_tileset',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('tileset-id', tilesetSource)

      const source = useSourceStore.getState().sources['tileset-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('tileset')
      // Widget should be null without spatialDataBounds
      expect(source?.widgets).toBeNull()
    })

    it('should handle raster source without required metadata', () => {
      const rasterSource: Partial<Source<AllExternalSources>> = {
        type: 'raster',
        data: 'test_raster',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('raster-id', rasterSource)

      const source = useSourceStore.getState().sources['raster-id']
      expect(source).toBeDefined()
      expect(source?.type).toBe('raster')
      // Widget should be null without rasterMetadata and spatialDataBounds
      expect(source?.widgets).toBeNull()
    })

    it('should use spatialDataType and spatialDataColumn from source', () => {
      const querySource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
        spatialDataType: 'h3',
        spatialDataColumn: 'h3_index',
      }

      useSourceStore.getState().setSource('query-h3', querySource)

      const source = useSourceStore.getState().sources[
        'query-h3'
      ] as Source<AllExternalSources>
      expect(source).toBeDefined()
      expect(source?.spatialDataType).toBe('h3')
      expect(source?.spatialDataColumn).toBe('h3_index')
    })

    it('should extract spatialDataType from type with + separator', () => {
      const querySource: Partial<Source<AllExternalSources>> = {
        type: 'query+h3',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('query-split', querySource)

      const source = useSourceStore.getState().sources['query-split']
      expect(source).toBeDefined()
      // Widget creation should handle the split type
      expect(source?.widgets).toBeDefined()
    })

    it('should destroy previous widget client when updating source', () => {
      const initialSource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('update-id', initialSource)
      const firstWidget =
        useSourceStore.getState().sources['update-id']?.widgets

      expect(firstWidget).toBeDefined()

      // Update the source
      const updatedSource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM updated_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('update-id', updatedSource)
      const secondWidget =
        useSourceStore.getState().sources['update-id']?.widgets

      // Should have created a new widget client
      expect(secondWidget).toBeDefined()
      expect(useSourceStore.getState().sources['update-id']?.data).toBe(
        'SELECT * FROM updated_table',
      )
    })
  })

  describe('removeSource with widget cleanup', () => {
    it('should destroy widget client when removing source', () => {
      const querySource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('cleanup-id', querySource)
      expect(useSourceStore.getState().sources['cleanup-id']).toBeDefined()

      useSourceStore.getState().removeSource('cleanup-id')
      expect(useSourceStore.getState().sources['cleanup-id']).toBeUndefined()
    })
  })

  describe('default values', () => {
    it('should apply default API values to sources', () => {
      const minimalSource: Partial<Source> = {
        type: 'table',
        data: 'test_table',
      }

      useSourceStore.getState().setSource('defaults-id', minimalSource)

      const source = useSourceStore.getState().sources[
        'defaults-id'
      ] as Source<AllExternalSources>
      expect(source?.apiBaseUrl).toBeDefined()
      expect(source?.clientId).toBeDefined()
      expect(source?.apiVersion).toBeDefined()
    })
  })

  describe('getSpatialDataFallback edge cases', () => {
    it('should handle type with + separator for h3', () => {
      const h3Source: Partial<Source<AllExternalSources>> = {
        type: 'query+h3',
        data: 'SELECT * FROM h3_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('h3-split', h3Source)

      const source = useSourceStore.getState().sources['h3-split']
      expect(source).toBeDefined()
      expect(source?.widgets).toBeDefined()
    })

    it('should handle type with + separator for quadbin', () => {
      const quadbinSource: Partial<Source<AllExternalSources>> = {
        type: 'table+quadbin',
        data: 'quadbin_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('quadbin-split', quadbinSource)

      const source = useSourceStore.getState().sources['quadbin-split']
      expect(source).toBeDefined()
      expect(source?.widgets).toBeDefined()
    })

    it('should use explicit spatialDataType and spatialDataColumn over type fallback', () => {
      const customSource: Partial<Source<AllExternalSources>> = {
        type: 'query+h3',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
        spatialDataType: 'quadbin',
        spatialDataColumn: 'custom_column',
      }

      useSourceStore.getState().setSource('custom-spatial', customSource)

      const source = useSourceStore.getState().sources[
        'custom-spatial'
      ] as Source<AllExternalSources>
      expect(source?.spatialDataType).toBe('quadbin')
      expect(source?.spatialDataColumn).toBe('custom_column')
    })

    it('should default to geo/geom when no spatial data specified', () => {
      const defaultSource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM geo_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('default-spatial', defaultSource)

      const source = useSourceStore.getState().sources['default-spatial']
      expect(source).toBeDefined()
      expect(source?.widgets).toBeDefined()
    })
  })

  describe('tileset and raster widget creation edge cases', () => {
    it('should create widget for tileset with spatialDataBounds', () => {
      const tilesetSource: Partial<Source<TilesetSource>> = {
        type: 'tileset',
        data: 'test_tileset',
        connectionName: 'test-connection',
        spatialDataBounds: [-180, -90, 180, 90],
      }

      useSourceStore.getState().setSource('tileset-valid', tilesetSource)

      const source = useSourceStore.getState().sources['tileset-valid']
      expect(source?.widgets).toBeDefined()
      expect(source?.widgets).not.toBeNull()
    })

    it('should create widget for raster with both metadata and bounds', () => {
      const rasterSource: Partial<Source<RasterSource>> = {
        type: 'raster',
        data: 'test_raster',
        connectionName: 'test-connection',
        rasterMetadata: {
          // @ts-expect-error - testing purposes
          bands: [{ name: 'band1', type: 'diverging' }],
        },
        spatialDataBounds: [-180, -90, 180, 90],
      }

      useSourceStore.getState().setSource('raster-valid', rasterSource)

      const source = useSourceStore.getState().sources['raster-valid']
      expect(source?.widgets).toBeDefined()
      expect(source?.widgets).not.toBeNull()
    })

    it('should return null widget for raster without rasterMetadata', () => {
      const rasterSource: Partial<Source<RasterSource>> = {
        type: 'raster',
        data: 'test_raster',
        connectionName: 'test-connection',
        spatialDataBounds: [-180, -90, 180, 90],
      }

      useSourceStore.getState().setSource('raster-no-metadata', rasterSource)

      const source = useSourceStore.getState().sources['raster-no-metadata']
      expect(source?.widgets).toBeNull()
    })

    it('should return null widget for raster without spatialDataBounds', () => {
      const rasterSource: Partial<Source<RasterSource>> = {
        type: 'raster',
        data: 'test_raster',
        connectionName: 'test-connection',
        rasterMetadata: {
          // @ts-expect-error - testing purposes
          bands: [{ name: 'band1', type: 'diverging' }],
        },
      }

      useSourceStore.getState().setSource('raster-no-bounds', rasterSource)

      const source = useSourceStore.getState().sources['raster-no-bounds']
      expect(source?.widgets).toBeNull()
    })
  })

  describe('table+tileset compound source types', () => {
    it('should create widget for table+tileset source (routes to table handler)', () => {
      const source = {
        type: 'table+tileset',
        data: 'carto-demo-data.demo_tables.world_population',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.world_population',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('table-tileset', source)

      const result = useSourceStore.getState().sources['table-tileset']
      expect(result).toBeDefined()
      expect(result?.type).toBe('table+tileset')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should create widget for table+tileset+h3 source', () => {
      const source = {
        type: 'table+tileset+h3',
        data: 'carto-demo-data.demo_tables.h3_data',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.h3_data',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('table-tileset-h3', source)

      const result = useSourceStore.getState().sources['table-tileset-h3']
      expect(result).toBeDefined()
      expect(result?.type).toBe('table+tileset+h3')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should create widget for table+tileset+quadbin source', () => {
      const source = {
        type: 'table+tileset+quadbin',
        data: 'carto-demo-data.demo_tables.quadbin_data',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.quadbin_data',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('table-tileset-quadbin', source)

      const result = useSourceStore.getState().sources['table-tileset-quadbin']
      expect(result).toBeDefined()
      expect(result?.type).toBe('table+tileset+quadbin')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should use correct spatial data fallback for three-segment types', () => {
      // table+tileset should default to geo (tileset is a base type)
      const vectorSource = {
        type: 'table+tileset',
        data: 'test_table',
        connectionName: 'test-connection',
        tileset: 'test_tileset',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('fallback-vector', vectorSource)

      // table+tileset+h3 should use h3 as fallback
      const h3Source = {
        type: 'table+tileset+h3',
        data: 'test_table_h3',
        connectionName: 'test-connection',
        tileset: 'test_tileset_h3',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('fallback-h3', h3Source)

      // table+tileset+quadbin should use quadbin as fallback
      const quadbinSource = {
        type: 'table+tileset+quadbin',
        data: 'test_table_quadbin',
        connectionName: 'test-connection',
        tileset: 'test_tileset_quadbin',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('fallback-quadbin', quadbinSource)

      // All three should have widgets (routed through table handler)
      expect(
        useSourceStore.getState().sources['fallback-vector']?.widgets,
      ).not.toBeNull()
      expect(
        useSourceStore.getState().sources['fallback-h3']?.widgets,
      ).not.toBeNull()
      expect(
        useSourceStore.getState().sources['fallback-quadbin']?.widgets,
      ).not.toBeNull()
    })
  })

  describe('query+tileset compound source types', () => {
    it('should create widget for query+tileset source (routes to query handler)', () => {
      const source = {
        type: 'query+tileset',
        data: 'SELECT * FROM `carto-demo-data.demo_tables.world_population`',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.world_population',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('query-tileset', source)

      const result = useSourceStore.getState().sources['query-tileset']
      expect(result).toBeDefined()
      expect(result?.type).toBe('query+tileset')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should create widget for query+tileset+h3 source', () => {
      const source = {
        type: 'query+tileset+h3',
        data: 'SELECT * FROM `carto-demo-data.demo_tables.h3_data`',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.h3_data',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('query-tileset-h3', source)

      const result = useSourceStore.getState().sources['query-tileset-h3']
      expect(result).toBeDefined()
      expect(result?.type).toBe('query+tileset+h3')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should create widget for query+tileset+quadbin source', () => {
      const source = {
        type: 'query+tileset+quadbin',
        data: 'SELECT * FROM `carto-demo-data.demo_tables.quadbin_data`',
        connectionName: 'test-connection',
        tileset: 'carto-demo-data.demo_tilesets.quadbin_data',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>

      useSourceStore.getState().setSource('query-tileset-quadbin', source)

      const result = useSourceStore.getState().sources['query-tileset-quadbin']
      expect(result).toBeDefined()
      expect(result?.type).toBe('query+tileset+quadbin')
      expect(result?.widgets).toBeDefined()
      expect(result?.widgets).not.toBeNull()
    })

    it('should use correct spatial data fallback for three-segment types', () => {
      // query+tileset should default to geo (tileset is a base type)
      const vectorSource = {
        type: 'query+tileset',
        data: 'SELECT * FROM test_table',
        connectionName: 'test-connection',
        tileset: 'test_tileset',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('qt-fallback-vector', vectorSource)

      // query+tileset+h3 should use h3 as fallback
      const h3Source = {
        type: 'query+tileset+h3',
        data: 'SELECT * FROM test_table_h3',
        connectionName: 'test-connection',
        tileset: 'test_tileset_h3',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('qt-fallback-h3', h3Source)

      // query+tileset+quadbin should use quadbin as fallback
      const quadbinSource = {
        type: 'query+tileset+quadbin',
        data: 'SELECT * FROM test_table_quadbin',
        connectionName: 'test-connection',
        tileset: 'test_tileset_quadbin',
        tileFormat: TileFormat.BINARY,
        spatialDataBounds: [-180, -90, 180, 90],
      } as unknown as Partial<Source>
      useSourceStore.getState().setSource('qt-fallback-quadbin', quadbinSource)

      // All three should have widgets (routed through query handler)
      expect(
        useSourceStore.getState().sources['qt-fallback-vector']?.widgets,
      ).not.toBeNull()
      expect(
        useSourceStore.getState().sources['qt-fallback-h3']?.widgets,
      ).not.toBeNull()
      expect(
        useSourceStore.getState().sources['qt-fallback-quadbin']?.widgets,
      ).not.toBeNull()
    })
  })

  describe('removeFilters edge cases', () => {
    it('should handle removing filter with non-matching owner', () => {
      const filters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
          owner: 'widget-1',
        },
      ]

      useSourceStore.getState().setFilters('test-id', filters)
      expect(
        Object.keys(useSourceStore.getState().filters['test-id']!),
      ).toHaveLength(1)

      // Try to remove with different owner
      useSourceStore.getState().removeFilters('test-id', 'category', 'widget-2')

      // Filter should still exist if owner doesn't match (depends on removeFilter behavior)
      expect(useSourceStore.getState().filters['test-id']).toBeDefined()
    })

    it('should handle multiple filters with same column but different owners', () => {
      const filters1: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
          owner: 'widget-1',
        },
      ]
      const filters2: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['C', 'D'],
          owner: 'widget-2',
        },
      ]

      useSourceStore.getState().setFilters('multi-owner-id', filters1)
      useSourceStore.getState().setFilters('multi-owner-id', filters2)

      const sourceFilters = useSourceStore.getState().filters['multi-owner-id']
      expect(sourceFilters).toBeDefined()
    })
  })

  describe('invalidateSourcesCache edge cases', () => {
    it('should only process external source IDs', () => {
      const geojsonSource: Partial<Source> = {
        type: 'geojson',
        data: [
          {
            type: 'Feature',
            geometry: { type: 'Point', coordinates: [0, 0] },
            properties: {},
          },
        ],
      }
      const querySource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('geojson-1', geojsonSource)
      useSourceStore.getState().setSource('query-1', querySource)

      // Should only set headers on query source, not geojson
      useSourceStore
        .getState()
        .invalidateSourcesCache(['geojson-1', 'query-1'], { timeout: 0 })

      const geojsonAfter = useSourceStore.getState().sources[
        'geojson-1'
      ] as Source<AllExternalSources>
      const queryAfter = useSourceStore.getState().sources[
        'query-1'
      ] as Source<AllExternalSources>

      // Query source should have headers set
      expect(queryAfter.headers).toBeDefined()
      // GeoJSON should not have headers (or be unchanged)
      expect(geojsonAfter).toBeDefined()
    })

    it('should handle invalidation with only geojson sources', () => {
      const geojsonSource1: Partial<Source> = {
        type: 'geojson',
        data: [],
      }
      const geojsonSource2: Partial<Source> = {
        type: 'geojson',
        data: [],
      }

      useSourceStore.getState().setSource('geojson-1', geojsonSource1)
      useSourceStore.getState().setSource('geojson-2', geojsonSource2)

      // Should return early and not process anything
      useSourceStore
        .getState()
        .invalidateSourcesCache(['geojson-1', 'geojson-2'], { timeout: 0 })

      // Should not throw and complete successfully
      expect(true).toBe(true)
    })

    it('should clean up headers after timeout', () => {
      vi.useFakeTimers()

      const tableSource: Partial<Source<AllExternalSources>> = {
        type: 'table',
        data: 'test_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('timeout-test', tableSource)

      useSourceStore.getState().invalidateSourcesCache(['timeout-test'], {
        timeout: 100,
        maxAge: 5,
      })

      // Headers should be set initially
      let source = useSourceStore.getState().sources[
        'timeout-test'
      ] as Source<AllExternalSources>
      expect(source.headers).toBeDefined()

      // Advance timers past timeout
      vi.advanceTimersByTime(101)

      // Headers should be removed
      source = useSourceStore.getState().sources[
        'timeout-test'
      ] as Source<AllExternalSources>
      // After timeout, headers should be deleted by setSource call
      expect(source).toBeDefined()

      vi.useRealTimers()
    })
  })

  describe('removeFilters with no column', () => {
    it('should not modify state when filters do not exist', () => {
      useSourceStore.getState().removeFilters('non-existent-source', 'category')

      const stateAfter = useSourceStore.getState().filters
      // Should create empty filters for the source
      expect(stateAfter['non-existent-source']).toEqual({})
    })
  })

  describe('useSource edge cases', () => {
    it('should return undefined for non-existent source', () => {
      const source = useSourceStore.getState().sources['non-existent']
      expect(source).toBeUndefined()
    })

    it('should return source for existing ID', () => {
      const testSource: Partial<Source<AllExternalSources>> = {
        type: 'table',
        data: 'test_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('test-id', testSource)
      const source = useSourceStore.getState().sources['test-id']

      expect(source).toBeDefined()
      expect(source?.data).toBe('test_table')
    })
  })

  describe('useSourceWithFilters edge cases', () => {
    it('should return undefined when source does not exist', () => {
      // Directly test the logic without using the hook
      const source = useSourceStore.getState().sources['non-existent']
      const filters = useSourceStore.getState().filters['non-existent']

      // If source doesn't exist, useSourceWithFilters returns undefined
      expect(source).toBeUndefined()
      expect(filters).toBeUndefined()
    })

    it('should merge source with filters when both exist', () => {
      const testSource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
      }

      const testFilters: AddFilterOptions[] = [
        {
          column: 'category',
          type: FilterType.IN,
          values: ['A', 'B'],
        },
      ]

      useSourceStore.getState().setSource('merge-test', testSource)
      useSourceStore.getState().setFilters('merge-test', testFilters)

      const source = useSourceStore.getState().sources['merge-test']
      const filters = useSourceStore.getState().filters['merge-test']

      expect(source).toBeDefined()
      expect(filters).toBeDefined()

      // The merged result would have both source and filters
      const merged = { ...source, filters: filters ?? {} }
      expect(merged.data).toBe('SELECT * FROM table')
      expect(merged.filters).toBeDefined()
    })

    it('should use empty filters object when filters dont exist', () => {
      const testSource: Partial<Source<AllExternalSources>> = {
        type: 'table',
        data: 'test_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('no-filters', testSource)

      const source = useSourceStore.getState().sources['no-filters']
      const filters = useSourceStore.getState().filters['no-filters']

      expect(source).toBeDefined()
      expect(filters).toBeUndefined()

      // useSourceWithFilters would use empty object for filters
      const merged = { ...source, filters: filters ?? {} }
      expect(merged.filters).toEqual({})
    })
  })

  describe('getFilterValues with different types', () => {
    it('should return empty array for undefined filters', () => {
      const values = getFilterValues(undefined, {
        column: 'test',
        type: FilterType.IN,
      })
      expect(values).toEqual([])
    })

    it('should return empty array for non-existent column', () => {
      const filters: Filters = {
        category: {
          in: { owner: 'test', values: ['A'] },
        },
      }

      const values = getFilterValues(filters, {
        column: 'non-existent',
        type: FilterType.IN,
      })
      expect(values).toEqual([])
    })

    it('should return empty array for non-existent filter type', () => {
      const filters: Filters = {
        category: {
          in: { owner: 'test', values: ['A'] },
        },
      }

      const values = getFilterValues(filters, {
        column: 'category',
        type: FilterType.BETWEEN,
      })
      expect(values).toEqual([])
    })

    it('should return values for existing filter', () => {
      const filters: Filters = {
        category: {
          in: { owner: 'test', values: ['A', 'B', 'C'] },
        },
      }

      const values = getFilterValues(filters, {
        column: 'category',
        type: FilterType.IN,
      })
      expect(values).toEqual(['A', 'B', 'C'])
    })
  })

  describe('setSource with missing optional fields', () => {
    it('should handle source without spatialDataType or spatialDataColumn', () => {
      const plainSource: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM simple_table',
        connectionName: 'test-connection',
      }

      useSourceStore.getState().setSource('plain-source', plainSource)

      const source = useSourceStore.getState().sources['plain-source']
      expect(source).toBeDefined()
      expect(source?.data).toBe('SELECT * FROM simple_table')
    })

    it('should handle source with only spatialDataType', () => {
      const source: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
        spatialDataType: 'h3',
      }

      useSourceStore.getState().setSource('spatial-type-only', source)

      const result = useSourceStore.getState().sources[
        'spatial-type-only'
      ] as Source<AllExternalSources>
      expect(result?.spatialDataType).toBe('h3')
    })

    it('should handle source with only spatialDataColumn', () => {
      const source: Partial<Source<AllExternalSources>> = {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'test-connection',
        spatialDataColumn: 'custom_geom',
      }

      useSourceStore.getState().setSource('spatial-column-only', source)

      const result = useSourceStore.getState().sources[
        'spatial-column-only'
      ] as Source<AllExternalSources>
      expect(result?.spatialDataColumn).toBe('custom_geom')
    })
  })
})
