import { useLockFn } from 'ahooks'
import useSWR, { mutate } from 'swr'
import { getVersion } from 'tauri-plugin-mihomo-api'

import {
  getClashInfo,
  getRuntimeConfig,
  patchClashConfig,
} from '@/services/cmds'

const PORT_KEYS = [
  'port',
  'socks-port',
  'mixed-port',
  'redir-port',
  'tproxy-port',
] as const

type ClashInfoPatch = Partial<
  Pick<
    IConfigData,
    | 'port'
    | 'socks-port'
    | 'mixed-port'
    | 'redir-port'
    | 'tproxy-port'
    | 'external-controller'
    | 'secret'
  >
>

const hasClashInfoPayload = (patch: ClashInfoPatch) =>
  PORT_KEYS.some((key) => patch[key] != null) ||
  patch['external-controller'] != null ||
  patch.secret != null

const validatePortRange = (port: number) => {
  if (port < 1000) {
    throw new Error('The port should not < 1000')
  }
  if (port > 65536) {
    throw new Error('The port should not > 65536')
  }
}

const validatePorts = (patch: ClashInfoPatch) => {
  PORT_KEYS.forEach((key) => {
    const port = patch[key]
    if (!port) return
    validatePortRange(port)
  })
}

export const useRuntimeConfig = (shouldFetch: boolean = true) => {
  return useSWR(shouldFetch ? 'getRuntimeConfig' : null, getRuntimeConfig)
}

export const useClash = () => {
  const { data: clash, mutate: mutateClash } = useRuntimeConfig()

  const { data: versionData, mutate: mutateVersion } = useSWR(
    'getVersion',
    getVersion,
  )

  const patchClash = useLockFn(async (patch: Partial<IConfigData>) => {
    await patchClashConfig(patch)
    mutateClash()
  })

  const version = versionData?.meta
    ? `${versionData.version} Mihomo`
    : versionData?.version || '-'

  return {
    clash,
    version,
    mutateClash,
    mutateVersion,
    patchClash,
  }
}

export const useClashInfo = () => {
  const { data: clashInfo, mutate: mutateInfo } = useSWR(
    'getClashInfo',
    getClashInfo,
  )

  const patchInfo = async (patch: ClashInfoPatch) => {
    if (!hasClashInfoPayload(patch)) return

    validatePorts(patch)

    await patchClashConfig(patch)
    mutateInfo()
    mutate('getClashConfig')
  }

  return {
    clashInfo,
    mutateInfo,
    patchInfo,
  }
}
