import { QueryClient } from '@tanstack/react-query'
import { match } from 'path-to-regexp'

import { fetchPost, fetchPosts, fetchPostChanges } from '@/lib/posts'
import { postsKeys, tagsKeys, wikiKeys } from '@/lib/queryKeys'
import { fetchNicoTags, fetchTagByName, fetchTag, fetchTagChanges, fetchTags } from '@/lib/tags'
import { fetchWikiPage,
	 fetchWikiPageByTitle,
	 fetchWikiPages } from '@/lib/wiki'

import type { Category, FetchPostsOrder, FetchTagsOrder } from '@/types'

type Prefetcher = (qc: QueryClient, url: URL) => Promise<void>

const mPost = match<{ id: string }> ('/posts/:id')
const mWiki = match<{ title: string }> ('/wiki/:title')
const mTag = match<{ id: string }> ('/tags/:id')


const boolFromQuery = (value: string | null): boolean =>
    ['1', 'true', 'on', 'yes', ''].includes ((value ?? '').toLowerCase ())


const prefetchWikiPagesIndex: Prefetcher = async (qc, url) => {
  const title = url.searchParams.get ('title') ?? ''

  await qc.prefetchQuery ({
      queryKey: wikiKeys.index ({ title }),
      queryFn:  () => fetchWikiPages ({ title }) })
}


const prefetchWikiPageShow: Prefetcher = async (qc, url) => {
  const m = mWiki (url.pathname)
  if (!(m))
    return

  const title = decodeURIComponent (m.params.title)
  const version = url.searchParams.get ('version') || undefined

  const wikiPage = await qc.fetchQuery ({
      queryKey: wikiKeys.show (title, { version }),
      queryFn:  () => fetchWikiPageByTitle (title, { version }) })

  if (wikiPage)
    {
      const effectiveId = String (wikiPage.id ?? '')
      await qc.prefetchQuery ({
	  queryKey: wikiKeys.show (effectiveId, { }),
	  queryFn: () => fetchWikiPage (effectiveId, { } ) })

      if (wikiPage.body)
	{
	  await qc.prefetchQuery ({
	      queryKey: wikiKeys.index ({ }),
	      queryFn: () => fetchWikiPages ({ }) })
	}
    }

  const effectiveTitle = wikiPage?.title ?? title

  await qc.prefetchQuery ({
      queryKey: tagsKeys.show (effectiveTitle),
      queryFn: () => fetchTagByName (effectiveTitle) })

  if (version)
    return

  const p = {
      tags: effectiveTitle,
      match: 'all',
      page: 1,
      limit: 8,
      url: '',
      title: '',
      originalCreatedFrom: '',
      originalCreatedTo: '',
      createdFrom: '',
      createdTo: '',
      updatedFrom: '',
      updatedTo: '',
      order: 'original_created_at:desc' } as const
  await qc.prefetchQuery ({
      queryKey: postsKeys.index (p),
      queryFn:  () => fetchPosts (p) })
}


const prefetchPostsIndex: Prefetcher = async (qc, url) => {
  const tags = url.searchParams.get ('tags') ?? ''
  const qURL = url.searchParams.get ('url') ?? ''
  const title = url.searchParams.get ('title') ?? ''
  const originalCreatedFrom = url.searchParams.get ('original_created_from') ?? ''
  const originalCreatedTo = url.searchParams.get ('original_created_to') ?? ''
  const createdFrom = url.searchParams.get ('created_from') ?? ''
  const createdTo = url.searchParams.get ('created_to') ?? ''
  const updatedFrom = url.searchParams.get ('updated_from') ?? ''
  const updatedTo = url.searchParams.get ('updated_to') ?? ''
  const m: 'all' | 'any' = url.searchParams.get ('match') === 'any' ? 'any' : 'all'
  const page = Number (url.searchParams.get ('page') || 1)
  const limit = Number (url.searchParams.get ('limit') || 20)
  const order = (url.searchParams.get ('order') ?? 'original_created_at:desc') as FetchPostsOrder

  const keys = {
      tags, match: m, page, limit, url: qURL, title,
      originalCreatedFrom, originalCreatedTo, createdFrom, createdTo,
      updatedFrom, updatedTo, order }

  await qc.prefetchQuery ({
      queryKey: postsKeys.index (keys),
      queryFn:  () => fetchPosts (keys) })
}


const prefetchPostShow: Prefetcher = async (qc, url) => {
  const m = mPost (url.pathname)
  if (!(m))
    return

  const { id } = m.params

  await qc.prefetchQuery ({
      queryKey: postsKeys.show (id),
      queryFn:  () => fetchPost (id) })
}


const prefetchPostChanges: Prefetcher = async (qc, url) => {
  const id = url.searchParams.get ('id')
  const tag = url.searchParams.get ('tag')
  const page = Number (url.searchParams.get ('page') || 1)
  const limit = Number (url.searchParams.get ('limit') || 20)

  if (tag)
    {
      await qc.prefetchQuery ({
	  queryKey: tagsKeys.show (tag),
	  queryFn: () => fetchTag (tag) })
    }

  await qc.prefetchQuery ({
      queryKey: postsKeys.changes ({ ...(id && { id }),
				     ...(tag && { tag }),
				     page, limit }),
      queryFn:  () => fetchPostChanges ({ ...(id && { id }),
					  ...(tag && { tag }),
					  page, limit }) })
}


const prefetchTagsIndex: Prefetcher = async (qc, url) => {
  const postRaw = url.searchParams.get ('post')
  const post = postRaw ? Number (postRaw) : null
  const name = url.searchParams.get ('name') ?? ''
  const category = (url.searchParams.get ('category') || null) as Category | null
  const postCountGTE = Number (url.searchParams.get ('post_count_gte') || 1)
  const postCountLTERaw = url.searchParams.get ('post_count_lte')
  const postCountLTE = postCountLTERaw ? Number (postCountLTERaw) : null
  const createdFrom = url.searchParams.get ('created_from') ?? ''
  const createdTo = url.searchParams.get ('created_to') ?? ''
  const updatedFrom = url.searchParams.get ('updated_from') ?? ''
  const updatedTo = url.searchParams.get ('updated_to') ?? ''
  const deprecated = url.searchParams.has ('deprecated')
      ? boolFromQuery (url.searchParams.get ('deprecated'))
      : null
  const page = Number (url.searchParams.get ('page') || 1)
  const limit = Number (url.searchParams.get ('limit') || 20)
  const order = (url.searchParams.get ('order') ?? 'post_count:desc') as FetchTagsOrder

  const keys = {
      post, name, category, postCountGTE, postCountLTE, createdFrom, createdTo,
      updatedFrom, updatedTo, deprecated, page, limit, order }

  await qc.prefetchQuery ({
      queryKey: tagsKeys.index (keys),
      queryFn:  () => fetchTags (keys) })
}


const prefetchNicoTagsIndex: Prefetcher = async (qc, url) => {
  const keys = {
      name:       url.searchParams.get ('name') ?? '',
      linkedTag:  url.searchParams.get ('linked_tag') ?? '',
      linkStatus: (url.searchParams.get ('link_status') || 'all') as
		  'all' | 'linked' | 'unlinked',
      page:       Number (url.searchParams.get ('page') || 1),
      limit:      Number (url.searchParams.get ('limit') || 20),
      order:      (url.searchParams.get ('order') || 'updated_at:desc') as
		  'name:asc' | 'name:desc' | 'created_at:asc' | 'created_at:desc'
		  | 'updated_at:asc' | 'updated_at:desc' }

  await qc.prefetchQuery ({
      queryKey: tagsKeys.nicoIndex (keys),
      queryFn:  () => fetchNicoTags (keys) })
}


const prefetchTagShow: Prefetcher = async (qc, url) => {
  const m = mTag (url.pathname)
  if (!(m))
    return

  const { id } = m.params

  await qc.prefetchQuery ({
      queryKey: tagsKeys.show (id),
      queryFn:  () => fetchTag (id) })
}


const prefetchTagChanges: Prefetcher = async (qc, url) => {
  const id = url.searchParams.get ('id')
  const page = Number (url.searchParams.get ('page') || 1)
  const limit = Number (url.searchParams.get ('limit') || 20)

  await qc.prefetchQuery ({
      queryKey: tagsKeys.changes ({ ...(id && { id }), page, limit }),
      queryFn: () => fetchTagChanges ({ ...(id && { id }), page, limit }) })
}


export const routePrefetchers: { test: (u: URL) => boolean; run: Prefetcher }[] = [
    { test: u => ['/', '/posts', '/posts/search'].includes (u.pathname),
      run: prefetchPostsIndex },
    { test: u => (!(['/posts/new', '/posts/changes', '/posts/search'].includes (u.pathname))
		  && Boolean (mPost (u.pathname))),
      run: prefetchPostShow },
    { test: u => u.pathname === '/posts/changes', run: prefetchPostChanges },
    { test: u => u.pathname === '/wiki', run: prefetchWikiPagesIndex },
    { test: u => (!(['/wiki/new', '/wiki/changes'].includes (u.pathname))
		  && Boolean (mWiki (u.pathname))),
      run: prefetchWikiPageShow },
    { test: u => u.pathname === '/tags', run: prefetchTagsIndex },
    { test: u => ['/tags/nico', '/nico/tags'].includes (u.pathname),
      run: prefetchNicoTagsIndex },
    { test: u => (!(['/tags/nico', '/tags/changes'].includes (u.pathname))
		  && Boolean (mTag (u.pathname))),
      run: prefetchTagShow },
    { test: u => u.pathname === '/tags/changes', run: prefetchTagChanges }]


export const prefetchForURL = async (qc: QueryClient, urlLike: string): Promise<void> => {
  const u = new URL (urlLike, location.origin)
  const r = routePrefetchers.find (x => x.test (u))
  if (!(r))
    return

  await r.run (qc, u)
}
