import { describe, it, expect } from 'vitest'
import { WidgetGeoJsonSourceImpl } from './widget-geojson-source-impl'
import type { Feature } from 'geojson'

describe('WidgetGeoJsonSourceImpl', () => {
  const mockFeatures: Feature[] = [
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [0, 0] },
      properties: { category: 'A', value: 10, count: 1 },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [1, 1] },
      properties: { category: 'B', value: 20, count: 2 },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [2, 2] },
      properties: { category: 'A', value: 30, count: 3 },
    },
    {
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [3, 3] },
      properties: { category: 'C', value: 40, count: 4 },
    },
  ]

  const baseProps = {
    accessToken: 'test-token',
    apiBaseUrl: 'https://api.test.com',
    connectionName: 'test-connection',
    clientId: 'test-client',
  }

  describe('constructor', () => {
    it('should create instance with feature array', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })

      expect(impl).toBeDefined()
    })

    it('should handle empty feature array', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: [],
      })

      expect(impl).toBeDefined()
    })
  })

  describe('destroy', () => {
    it('should clean up resources', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })

      expect(() => impl.destroy()).not.toThrow()
    })

    it('should be callable multiple times', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })

      impl.destroy()
      expect(() => impl.destroy()).not.toThrow()
    })
  })

  describe('with filters', () => {
    it('should accept filters in constructor', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A'],
            },
          },
        },
      })

      expect(impl).toBeDefined()
    })

    it('should handle multiple filters', () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A', 'B'],
            },
          },
          value: {
            between: {
              owner: 'test',
              values: [[10, 30]],
            },
          },
        },
      })

      expect(impl).toBeDefined()
    })
  })

  describe('edge cases', () => {
    it('should handle features with null properties', () => {
      const featuresWithNull: Feature[] = [
        {
          type: 'Feature',
          geometry: { type: 'Point', coordinates: [0, 0] },
          properties: null,
        },
      ]

      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: featuresWithNull,
      })

      expect(impl).toBeDefined()
    })

    it('should handle features with undefined properties', () => {
      const featuresWithoutProps: Feature[] = [
        {
          type: 'Feature',
          geometry: { type: 'Point', coordinates: [0, 0] },
          properties: {},
        },
      ]

      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: featuresWithoutProps,
      })

      expect(impl).toBeDefined()
    })

    it('should handle features with complex geometries', () => {
      const complexFeatures: Feature[] = [
        {
          type: 'Feature',
          geometry: {
            type: 'Polygon',
            coordinates: [
              [
                [0, 0],
                [1, 0],
                [1, 1],
                [0, 1],
                [0, 0],
              ],
            ],
          },
          properties: { id: 1 },
        },
      ]

      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: complexFeatures,
      })

      expect(impl).toBeDefined()
    })
  })

  describe('loadGeoJSON', () => {
    it('should load GeoJSON features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })

      impl.loadGeoJSON({ geojson: mockFeatures })

      // Verify features were loaded by checking count
      const result = await impl.getFormula({ operation: 'count' })
      expect(result.value).toBe(4)
    })

    it('should replace existing features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })

      const newFeatures: Feature[] = [
        {
          type: 'Feature',
          geometry: { type: 'Point', coordinates: [5, 5] },
          properties: { value: 50 },
        },
      ]

      impl.loadGeoJSON({ geojson: newFeatures })

      // Verify features were replaced by checking count
      const result = await impl.getFormula({ operation: 'count' })
      expect(result.value).toBe(1)
    })
  })

  describe('getFormula', () => {
    it('should return count of all features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({
        operation: 'count',
      })

      expect(result.value).toBe(4)
    })

    it('should return null for empty filtered features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({
        operation: 'count',
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['NonExistent'],
            },
          },
        },
      })

      expect(result.value).toBeNull()
    })

    it('should throw error for custom aggregation', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      try {
        await impl.getFormula({
          operation: 'custom',
        })
        // Should not reach here
        expect(true).toBe(false)
      } catch (error) {
        expect((error as Error).message).toContain(
          'Custom aggregation not supported',
        )
      }
    })

    it('should handle count operation with * column', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({
        operation: 'count',
        column: '*',
      })

      expect(result.value).toBe(4)
    })

    it('should apply spatial filter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({
        operation: 'count',
        spatialFilter: {
          type: 'Polygon',
          coordinates: [
            [
              [-1, -1],
              [1.5, -1],
              [1.5, 1.5],
              [-1, 1.5],
              [-1, -1],
            ],
          ],
        },
      })

      expect(result.value).toBeDefined()
    })
  })

  describe('getFeatures', () => {
    it('should reject with error', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })

      await expect(impl.getFeatures()).rejects.toThrow(
        'getFeatures not supported for geojson',
      )
    })
  })

  describe('spatial filtering', () => {
    it('should return all features when no spatial filter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({ operation: 'count' })

      expect(result.value).toBe(4)
    })

    it('should filter features by spatial geometry', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const spatialFilter = {
        type: 'Polygon' as const,
        coordinates: [
          [
            [-1, -1],
            [1.5, -1],
            [1.5, 1.5],
            [-1, 1.5],
            [-1, -1],
          ],
        ],
      }

      const result = await impl.getFormula({
        operation: 'count',
        spatialFilter,
      })

      // Should filter features within the polygon
      expect(result.value).toBeLessThanOrEqual(4)
    })
  })

  describe('getHistogram', () => {
    it('should return histogram data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getHistogram({
        column: 'value',
        ticks: [0, 10, 20, 30, 40],
      })

      expect(Array.isArray(result)).toBe(true)
      expect(result.length).toBeGreaterThan(0)
    })

    it('should use default operation count', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getHistogram({
        column: 'value',
        ticks: [0, 50],
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should apply join operation', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getHistogram({
        column: 'value',
        ticks: [0, 50],
        joinOperation: 'sum',
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should return empty array for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getHistogram({
        column: 'value',
        ticks: [0, 10, 20],
      })

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

    it('should apply filters', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getHistogram({
        column: 'value',
        ticks: [0, 50],
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A'],
            },
          },
        },
      })

      expect(Array.isArray(result)).toBe(true)
    })
  })

  describe('getCategories', () => {
    it('should return category data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
      })

      expect(Array.isArray(result)).toBe(true)
      expect(result.length).toBeGreaterThan(0)
    })

    it('should return empty array for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
      })

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

    it('should apply operation column', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operationColumn: 'value',
        operation: 'sum',
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should handle othersThreshold', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
        othersThreshold: 1,
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should handle rawResult', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
        rawResult: true,
      })

      expect(result).toBeDefined()
    })

    it('should handle orderBy parameter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
        orderBy: 'frequency_asc',
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should apply filters to categories', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operation: 'count',
        filters: {
          value: {
            between: {
              owner: 'test',
              values: [[10, 30]],
            },
          },
        },
      })

      expect(Array.isArray(result)).toBe(true)
    })
  })

  describe('getScatter', () => {
    it('should return scatter plot data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'count',
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should return empty array for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'count',
      })

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

    it('should apply filters', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'count',
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A'],
            },
          },
        },
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should handle join operations for axes', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'value',
        xAxisJoinOperation: 'sum',
        yAxisJoinOperation: 'avg',
      })

      expect(Array.isArray(result)).toBe(true)
    })

    it('should apply spatial filter to scatter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'count',
        spatialFilter: {
          type: 'Polygon',
          coordinates: [
            [
              [-1, -1],
              [5, -1],
              [5, 5],
              [-1, 5],
              [-1, -1],
            ],
          ],
        },
      })

      expect(Array.isArray(result)).toBe(true)
    })
  })

  describe('getTable', () => {
    it('should return table data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['category', 'value'],
      })

      expect(result.rows).toBeDefined()
      expect(result.totalCount).toBe(4)
    })

    it('should return empty for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getTable({
        columns: ['category'],
      })

      expect(result.rows).toEqual([])
      expect(result.totalCount).toBe(0)
    })

    it('should handle pagination', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['category'],
        offset: 1,
        limit: 2,
      })

      expect(result.rows.length).toBe(2)
      expect(result.totalCount).toBe(4)
    })

    it('should handle sorting', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['value'],
        sortBy: 'value',
        sortDirection: 'desc',
      })

      expect(result.rows.length).toBeGreaterThan(0)
    })

    it('should apply filters', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['category'],
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A'],
            },
          },
        },
      })

      expect(result.totalCount).toBeLessThan(4)
    })

    it('should handle deprecated search filter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['category', 'value'],
        searchFilterColumn: 'category',
        searchFilterText: 'A',
      })

      expect(result.totalCount).toBeLessThan(4)
    })

    it('should handle sortByColumnType', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getTable({
        columns: ['value'],
        sortBy: 'value',
        sortDirection: 'asc',
        sortByColumnType: 'number',
      })

      expect(result.rows.length).toBeGreaterThan(0)
    })
  })

  describe('getTimeSeries', () => {
    const mockTimeSeriesFeatures: Feature[] = [
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [0, 0] },
        properties: { timestamp: '2024-01-01', value: 10 },
      },
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [1, 1] },
        properties: { timestamp: '2024-01-02', value: 20 },
      },
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [2, 2] },
        properties: { timestamp: '2024-01-03', value: 30 },
      },
    ]

    it('should return time series data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockTimeSeriesFeatures,
      })
      impl.loadGeoJSON({ geojson: mockTimeSeriesFeatures })

      const result = await impl.getTimeSeries({
        column: 'timestamp',
        stepSize: 'day',
        operation: 'count',
      })

      expect(result.rows).toBeDefined()
    })

    it('should return empty rows for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getTimeSeries({
        column: 'timestamp',
        stepSize: 'day',
        operation: 'count',
      })

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

    it('should apply operation column', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockTimeSeriesFeatures,
      })
      impl.loadGeoJSON({ geojson: mockTimeSeriesFeatures })

      const result = await impl.getTimeSeries({
        column: 'timestamp',
        operationColumn: 'value',
        operation: 'sum',
        stepSize: 'day',
      })

      expect(result.rows).toBeDefined()
    })
  })

  describe('getRange', () => {
    it('should return range data', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getRange({
        column: 'value',
      })

      expect(result?.min).toBeDefined()
      expect(result?.max).toBeDefined()
    })

    it('should return null range for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({ ...baseProps, data: [] })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getRange({
        column: 'value',
      })

      expect(result).toBeNull()
    })

    it('should apply filters', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getRange({
        column: 'value',
        filters: {
          category: {
            in: {
              owner: 'test',
              values: ['A'],
            },
          },
        },
      })

      expect(result?.min).toBeDefined()
      expect(result?.max).toBeDefined()
    })

    it('should use filterOwner parameter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getRange({
        column: 'value',
        filterOwner: 'custom-owner',
        filters: {
          category: {
            in: {
              owner: 'custom-owner',
              values: ['A'],
            },
          },
        },
      })

      expect(result?.min).toBeDefined()
      expect(result?.max).toBeDefined()
    })

    it('should apply spatial filter to range', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getRange({
        column: 'value',
        spatialFilter: {
          type: 'Polygon',
          coordinates: [
            [
              [-1, -1],
              [5, -1],
              [5, 5],
              [-1, 5],
              [-1, -1],
            ],
          ],
        },
      })

      expect(result?.min).toBeDefined()
      expect(result?.max).toBeDefined()
    })
  })

  describe('getAggregations edge cases', () => {
    it('should return empty rows when features are empty', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: [],
      })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getAggregations({
        aggregations: [{ column: 'value', operation: 'sum', alias: 'total' }],
      })

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

    it('should calculate multiple aggregations', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getAggregations({
        aggregations: [
          { column: 'value', operation: 'sum', alias: 'total_value' },
          { column: 'count', operation: 'max', alias: 'max_count' },
          { column: '*', operation: 'count', alias: 'record_count' },
        ],
      })

      expect(result.rows).toHaveLength(1)
      expect(result.rows[0]).toHaveProperty('total_value')
      expect(result.rows[0]).toHaveProperty('max_count')
      expect(result.rows[0]).toHaveProperty('record_count')
      expect(result.rows[0]?.record_count).toBe(4)
    })

    it('should allow count operation without specific column', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getAggregations({
        aggregations: [{ column: '*', operation: 'count', alias: 'total' }],
      })

      expect(result).toBeDefined()
      expect(result.rows).toHaveLength(1)
      expect(result.rows[0]?.total).toBe(4)
    })
  })

  describe('getTimeSeries edge cases', () => {
    const timeSeriesFeatures: Feature[] = [
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [0, 0] },
        properties: { date: '2024-01-01', value: 10 },
      },
      {
        type: 'Feature',
        geometry: { type: 'Point', coordinates: [1, 1] },
        properties: { date: '2024-01-02', value: 20 },
      },
    ]

    it('should return empty rows for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: [],
      })

      const result = await impl.getTimeSeries({
        column: 'date',
        operation: 'sum',
        stepSize: 'day',
      })

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

    it('should work with date column and step size', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: timeSeriesFeatures,
      })

      const result = await impl.getTimeSeries({
        column: 'date',
        operation: 'count',
        stepSize: 'day',
      })

      expect(result.rows).toBeDefined()
    })
  })

  describe('getFormula edge cases', () => {
    it('should return null for empty features', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: [],
      })
      impl.loadGeoJSON({ geojson: [] })

      const result = await impl.getFormula({
        column: 'value',
        operation: 'sum',
      })

      expect(result.value).toBeNull()
    })
  })

  describe('assertColumn edge cases', () => {
    it('should handle empty column string', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      // Empty string should pass validation for backward compatibility
      const result = await impl.getFormula({
        column: '',
        operation: 'count',
      })

      expect(result).toBeDefined()
    })

    it('should validate columns exist in scatter', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getScatter({
        xAxisColumn: 'value',
        yAxisColumn: 'count',
      })

      expect(result).toBeDefined()
      expect(Array.isArray(result)).toBe(true)
    })

    it('should validate operationColumn in categories', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getCategories({
        column: 'category',
        operationColumn: 'value',
        operation: 'sum',
      })

      expect(result).toBeDefined()
      expect(Array.isArray(result)).toBe(true)
    })
  })

  describe('filtersLogicalOperator', () => {
    it('should apply filters with operator', async () => {
      const impl = new WidgetGeoJsonSourceImpl({
        ...baseProps,
        data: mockFeatures,
        filters: {
          category: { in: { owner: 'test', values: ['A'] } },
        },
      })
      impl.loadGeoJSON({ geojson: mockFeatures })

      const result = await impl.getFormula({
        column: '*',
        operation: 'count',
      })

      // Should match features where category='A'
      expect(result.value).toBe(2) // Two features with category A
    })
  })
})
