import type {
  AggregationsRequestOptions,
  AggregationsResponse,
  AggregationType,
} from '@carto/api-client'
import { createModel, type ModelQueryFactory } from './utils/create-model'
import { getModelQueryOptions } from './utils/get-model-query-options'
import { useSourceSlices } from './utils/use-source-slices'
import type {
  ModelComponentProps,
  ModelHookResponse,
  ModelProps,
} from './types'

export type AggregationsWidgetProps = Omit<
  AggregationsRequestOptions,
  'aggregations'
> & {
  aggregations: (
    | {
        operation: 'custom'
        aggregationExp: string
        alias: string
      }
    | {
        column: string
        operation: Exclude<AggregationType, 'custom'>
        alias: string
      }
  )[]
}

export const getAggregations: ModelQueryFactory<
  AggregationsWidgetProps,
  AggregationsResponse
> = (args) =>
  getModelQueryOptions<AggregationsWidgetProps, AggregationsResponse>({
    queryKeyPrefix: ['aggregations'],
    getWidgetMethod: (widgetModel) => (params) => {
      const aggregations = params.aggregations

      let adaptedAggregations: AggregationsRequestOptions['aggregations'] =
        aggregations.filter((agg) => agg.operation !== 'custom')

      if (aggregations.some((agg) => agg.operation === 'custom')) {
        adaptedAggregations = transformAggregationsToSql(aggregations).join(',')
      }

      const adaptedParams = {
        ...params,
        aggregations: adaptedAggregations,
      }

      return widgetModel.getAggregations(adaptedParams)
    },
    ...args,
  })

const { useModelHook: useAggregationsQuery } = createModel<
  AggregationsWidgetProps,
  AggregationsResponse
>({ buildQuery: getAggregations })

/**
 * `useAggregations` is a hook that performs multiple aggregations simultaneously on different columns in a single request.
 * `AggregationsModel` is the render-props component equivalent. Supports custom SQL aggregation expressions alongside standard operations.
 *
 * @example
 * ```tsx
 * <AggregationsModel
 *   accessToken={token}
 *   sourceId="my_source"
 *   widgetProps={{
 *     aggregations: [
 *       { column: 'revenue', operation: 'sum', alias: 'total_revenue' },
 *       { column: 'id', operation: 'count', alias: 'total_count' },
 *     ],
 *   }}
 * >
 *   {({ data }) => <MetricsDisplay data={data} />}
 * </AggregationsModel>
 * ```
 */
function useAggregations<
  P extends ModelProps<AggregationsWidgetProps, AggregationsResponse>,
>(props: P): ModelHookResponse<P, AggregationsResponse> {
  const { sourceId, sourcesId } = props
  const slices = useSourceSlices(sourceId ? [sourceId] : (sourcesId ?? []))
  return useAggregationsQuery(props, slices)
}

function AggregationsModel<
  P extends ModelProps<AggregationsWidgetProps, AggregationsResponse>,
>(
  props: ModelComponentProps<AggregationsWidgetProps, AggregationsResponse, P>,
): React.ReactElement {
  return props.children(useAggregations(props)) as React.ReactElement
}

export { useAggregations, AggregationsModel }

/**
 * Transforms aggregation objects into SQL expressions
 */
function transformAggregationsToSql(
  aggregations: AggregationsWidgetProps['aggregations'],
): string[] {
  return aggregations.map((agg) => {
    if (agg.operation === 'custom') {
      return `${agg.aggregationExp} AS ${agg.alias}`
    }

    const { column, operation, alias } = agg

    switch (operation) {
      case 'count':
        return `COUNT(${column}) AS ${alias}`
      case 'sum':
        return `SUM(${column}) AS ${alias}`
      case 'avg':
        return `AVG(${column}) AS ${alias}`
      case 'min':
        return `MIN(${column}) AS ${alias}`
      case 'max':
        return `MAX(${column}) AS ${alias}`
      default:
        // This should never happen if all AggregationType values are handled above
        throw new Error(
          `Unsupported aggregation operation: ${String(operation)}`,
        )
    }
  })
}
