import { getRoutes } from '../router/getRoutes'
import { isTypedRoute, removeSupportedExtensions } from '../router/matchers'
import type { RouteNode } from '../router/Route'
import type { One } from '../vite/types'

// /[...param1]/ - Match [...param1]
const CATCH_ALL = /\[\.\.\..+?\]/g
// /[param1] - Match [param1]
const SLUG = /\[.+?\]/g

export function getTypedRoutesDeclarationFile(ctx: One.RouteContext) {
  const staticRoutes = new Set<string>()
  const dynamicRoutes = new Set<string>()
  const dynamicRouteContextKeys = new Set<string>()

  walkRouteNode(
    getRoutes(ctx, {
      platformRoutes: false, // We don't need to generate platform specific routes
      ignoreEntryPoints: true,
      ignoreRequireErrors: true,
      // importMode: 'async',
    }),
    '',
    staticRoutes,
    dynamicRoutes,
    dynamicRouteContextKeys
  )

  return `// deno-lint-ignore-file
/* eslint-disable */
// biome-ignore: needed import
import type { OneRouter } from 'one'

declare module 'one' {
  export namespace OneRouter {
    export interface __routes<T extends string = string> extends Record<string, unknown> {
      StaticRoutes: ${setToUnionType(staticRoutes)}
      DynamicRoutes: ${setToUnionType(dynamicRoutes)}
      DynamicRouteTemplate: ${setToUnionType(dynamicRouteContextKeys)}
      IsTyped: true
    }
  }
}
`.trim()
}

/**
 * Walks a RouteNode tree and adds the routes to the provided sets
 */
function walkRouteNode(
  routeNode: RouteNode | null,
  parentRoutePath: string,
  staticRoutes: Set<string>,
  dynamicRoutes: Set<string>,
  dynamicRouteContextKeys: Set<string>
) {
  if (!routeNode) return

  addRouteNode(routeNode, parentRoutePath, staticRoutes, dynamicRoutes, dynamicRouteContextKeys)

  parentRoutePath = `${removeSupportedExtensions(`${parentRoutePath}/${routeNode.route}`).replace(/\/?index$/, '')}` // replace /index with /

  for (const child of routeNode.children) {
    walkRouteNode(child, parentRoutePath, staticRoutes, dynamicRoutes, dynamicRouteContextKeys)
  }
}

/**
 * Given a RouteNode, adds the route to the correct sets
 * Modifies the RouteNode.route to be a typed-route string
 */
function addRouteNode(
  routeNode: RouteNode | null,
  parentRoutePath: string,
  staticRoutes: Set<string>,
  dynamicRoutes: Set<string>,
  dynamicRouteContextKeys: Set<string>
) {
  if (!routeNode?.route) return
  if (!isTypedRoute(routeNode.route)) return

  let routePath = `${parentRoutePath}/${removeSupportedExtensions(routeNode.route).replace(/\/?index$/, '')}` // replace /index with /

  if (!routePath.startsWith('/')) {
    routePath = `/${routePath}`
  }

  if (routeNode.dynamic) {
    for (const path of generateCombinations(routePath)) {
      dynamicRouteContextKeys.add(path)
      dynamicRoutes.add(
        `${path.replaceAll(CATCH_ALL, '${string}').replaceAll(SLUG, '${OneRouter.SingleRoutePart<T>}')}`
      )
    }
  } else {
    for (const combination of generateCombinations(routePath)) {
      staticRoutes.add(combination)
    }
  }
}

/**
 * Converts a Set to a TypeScript union type
 */
const setToUnionType = <T>(set: Set<T>) => {
  return set.size > 0
    ? [...set]
        .sort()
        .map((s) => `\`${s}\``)
        .join(' | ')
    : 'never'
}

function generateCombinations(pathname) {
  const groups = pathname.split('/').filter((part) => part.startsWith('(') && part.endsWith(')'))
  const combinations: string[] = []

  function generate(currentIndex, currentPath) {
    if (currentIndex === groups.length) {
      combinations.push(currentPath.replace(/\/{2,}/g, '/'))
      return
    }

    const group = groups[currentIndex]
    const withoutGroup = currentPath.replace(`/${group}`, '')
    generate(currentIndex + 1, withoutGroup)
    generate(currentIndex + 1, currentPath)
  }

  generate(0, pathname)
  return combinations
}
