import { isAllOptional, isOptional } from '@kubb/oas'
import { Client } from '@kubb/plugin-client/components'
import type { OperationSchemas } from '@kubb/plugin-oas'
import { getPathParams } from '@kubb/plugin-oas/utils'
import { File, Function, FunctionParams } from '@kubb/react-fabric'
import type { FabricReactNode } from '@kubb/react-fabric/types'
import type { PluginSolidQuery } from '../types.ts'
import { QueryKey } from './QueryKey.tsx'

type Props = {
  name: string
  clientName: string
  queryKeyName: string
  typeSchemas: OperationSchemas
  paramsCasing: PluginSolidQuery['resolvedOptions']['paramsCasing']
  paramsType: PluginSolidQuery['resolvedOptions']['paramsType']
  pathParamsType: PluginSolidQuery['resolvedOptions']['pathParamsType']
  dataReturnType: PluginSolidQuery['resolvedOptions']['client']['dataReturnType']
}

type GetParamsProps = {
  paramsCasing: PluginSolidQuery['resolvedOptions']['paramsCasing']
  paramsType: PluginSolidQuery['resolvedOptions']['paramsType']
  pathParamsType: PluginSolidQuery['resolvedOptions']['pathParamsType']
  typeSchemas: OperationSchemas
}

function getParams({ paramsType, paramsCasing, pathParamsType, typeSchemas }: GetParamsProps) {
  if (paramsType === 'object') {
    const pathParams = getPathParams(typeSchemas.pathParams, {
      typed: true,
      casing: paramsCasing,
      override: (item) => ({ ...item, type: `${item.type} | undefined` }),
    })

    const children = {
      ...pathParams,
      data: typeSchemas.request?.name
        ? {
            type: typeSchemas.request?.name,
            optional: isOptional(typeSchemas.request?.schema),
          }
        : undefined,
      params: typeSchemas.queryParams?.name
        ? {
            type: typeSchemas.queryParams?.name,
            optional: isOptional(typeSchemas.queryParams?.schema),
          }
        : undefined,
      headers: typeSchemas.headerParams?.name
        ? {
            type: typeSchemas.headerParams?.name,
            optional: isOptional(typeSchemas.headerParams?.schema),
          }
        : undefined,
    }

    // Check if all children are optional or undefined
    const allChildrenAreOptional = Object.values(children).every((child) => !child || child.optional)

    return FunctionParams.factory({
      data: {
        mode: 'object',
        children,
        default: allChildrenAreOptional ? '{}' : undefined,
      },
      config: {
        type: typeSchemas.request?.name
          ? `Partial<RequestConfig<${typeSchemas.request?.name}>> & { client?: Client }`
          : 'Partial<RequestConfig> & { client?: Client }',
        default: '{}',
      },
    })
  }

  return FunctionParams.factory({
    pathParams: typeSchemas.pathParams?.name
      ? {
          mode: pathParamsType === 'object' ? 'object' : 'inlineSpread',
          children: getPathParams(typeSchemas.pathParams, {
            typed: true,
            casing: paramsCasing,
            override: (item) => ({ ...item, type: `${item.type} | undefined` }),
          }),
          default: isAllOptional(typeSchemas.pathParams?.schema) ? '{}' : undefined,
        }
      : undefined,
    data: typeSchemas.request?.name
      ? {
          type: typeSchemas.request?.name,
          optional: isOptional(typeSchemas.request?.schema),
        }
      : undefined,
    params: typeSchemas.queryParams?.name
      ? {
          type: typeSchemas.queryParams?.name,
          optional: isOptional(typeSchemas.queryParams?.schema),
        }
      : undefined,
    headers: typeSchemas.headerParams?.name
      ? {
          type: typeSchemas.headerParams?.name,
          optional: isOptional(typeSchemas.headerParams?.schema),
        }
      : undefined,
    config: {
      type: typeSchemas.request?.name
        ? `Partial<RequestConfig<${typeSchemas.request?.name}>> & { client?: Client }`
        : 'Partial<RequestConfig> & { client?: Client }',
      default: '{}',
    },
  })
}

export function QueryOptions({
  name,
  clientName,
  typeSchemas,
  paramsCasing,
  paramsType,
  dataReturnType,
  pathParamsType,
  queryKeyName,
}: Props): FabricReactNode {
  const TData = dataReturnType === 'data' ? typeSchemas.response.name : `ResponseConfig<${typeSchemas.response.name}>`
  const TError = `ResponseErrorConfig<${typeSchemas.errors?.map((item) => item.name).join(' | ') || 'Error'}>`

  const params = getParams({ paramsType, paramsCasing, pathParamsType, typeSchemas })
  const clientParams = Client.getParams({
    paramsCasing,
    typeSchemas,
    paramsType,
    pathParamsType,
    isConfigurable: true,
  })
  const queryKeyParams = QueryKey.getParams({
    pathParamsType,
    paramsCasing,
    typeSchemas,
  })

  const enabledPathParams = getPathParams(typeSchemas.pathParams, { casing: paramsCasing })
  const enabledParamNames = new Set(
    Object.entries(enabledPathParams)
      // Only include if the parameter exists and is NOT optional
      // This ensures we only check required parameters
      .filter(([, item]) => item && !item.optional && !item.default)
      .map(([key]) => key),
  )
  const enabled = [...enabledParamNames].join(' && ')

  const enabledText = enabled ? `enabled: !!(${enabled}),` : ''

  return (
    <File.Source name={name} isExportable isIndexable>
      <Function name={name} export params={params.toConstructor()}>
        {`
      const queryKey = ${queryKeyName}(${queryKeyParams.toCall()})
      return queryOptions<${TData}, ${TError}, ${TData}, typeof queryKey>({
      ${enabledText}
       queryKey,
       queryFn: async ({ signal }) => {
          return ${clientName}(${clientParams.toCall({
            transformName(name) {
              if (name === 'config') {
                return '{ ...config, signal: config.signal ?? signal }'
              }

              return enabledParamNames.has(name) ? `${name}!` : name
            },
          })})
       },
      })
`}
      </Function>
    </File.Source>
  )
}

QueryOptions.getParams = getParams
