import type {
  PluginContract,
  DataTypeContract,
  DiscoveryResult,
} from "./contractTypes"

// Store plugin contracts: pluginKey -> contract
const contracts: Record<string, PluginContract> = {}

export const registerPluginContract = (
  pluginKey: string,
  contract: PluginContract,
): void => {
  if (contracts[pluginKey]) {
    throw new Error(`Plugin contract for "${pluginKey}" is already registered. Make sure that each plugin is calling fastify.generatePluginContract only once! Multiple calls are not allowed.`)
  }

  contracts[pluginKey] = contract
}

export const getPluginContract = (
  pluginKey: string,
): PluginContract | undefined => contracts[pluginKey]

export const getDataTypeContract = (
  pluginKey: string,
  dataTypeName: string,
): DataTypeContract | undefined => contracts[pluginKey]?.[dataTypeName]

export const getAllPluginContracts = (): Record<string, PluginContract> =>
  contracts

export const getAllPluginKeys = (): string[] => Object.keys(contracts)

export const getAvailableDataTypes = (): Record<string, string[]> => {
  const result: Record<string, string[]> = {}

  Object.entries(contracts).forEach(([pluginKey, contract]) => {
    result[pluginKey] = Object.keys(contract)
  })

  return result
}

export const getDataTypeOperations = (
  pluginKey: string,
  dataTypeName: string,
): string[] => {
  const dataType = getDataTypeContract(pluginKey, dataTypeName)
  return dataType?.operations ? Object.keys(dataType.operations) : []
}

export const callDataTypeOperation = async (
  pluginKey: string,
  dataTypeName: string,
  operation: string,
  ...args: any[]
): Promise<any> => {
  const dataType = getDataTypeContract(pluginKey, dataTypeName)

  if (!dataType) {
    throw new Error(
      `DataType "${dataTypeName}" not found in plugin "${pluginKey}"`,
    )
  }

  const operationFn = dataType.operations?.[operation]
  if (!operationFn) {
    throw new Error(
      `Operation "${operation}" not provided by "${pluginKey}.${dataTypeName}"`,
    )
  }

  return await operationFn(...args)
}

// Discovery functions
export const findDataTypesByKeyword = (keyword: string): DiscoveryResult[] => {
  const results: DiscoveryResult[] = []

  Object.entries(contracts).forEach(([pluginKey, contract]) => {
    Object.entries(contract).forEach(([dataTypeName, dataType]) => {
      if (dataType.keywords?.includes(keyword)) {
        results.push({
          pluginKey,
          dataTypeName,
          keywords: dataType.keywords || [],
          operations: dataType.operations
            ? Object.keys(dataType.operations)
            : [],
        })
      }
    })
  })

  return results
}

export const findDataTypesWithOperation = (
  operation: string,
): DiscoveryResult[] => {
  const results: DiscoveryResult[] = []

  Object.entries(contracts).forEach(([pluginKey, contract]) => {
    Object.entries(contract).forEach(([dataTypeName, dataType]) => {
      if (dataType.operations?.[operation]) {
        results.push({
          pluginKey,
          dataTypeName,
          keywords: dataType.keywords || [],
          operations: Object.keys(dataType.operations),
        })
      }
    })
  })

  return results
}

export const getAllKeywords = (): string[] => {
  const keywordSet = new Set<string>()

  Object.values(contracts).forEach((contract) => {
    Object.values(contract).forEach((dataType) => {
      dataType.keywords?.forEach((keyword) => keywordSet.add(keyword))
    })
  })

  return Array.from(keywordSet)
}

// Utility function to check if operation exists
export const hasOperation = (
  pluginKey: string,
  dataTypeName: string,
  operation: string,
): boolean => {
  const dataType = getDataTypeContract(pluginKey, dataTypeName)
  return !!dataType?.operations?.[operation]
}
