import type { One } from '../vite/types'

/** Match `[page]` -> `page` or `[...page]` -> `page` with deep flag */
const dynamicNameRe = /^\[([^[\]]+?)\]$/

export interface DynamicNameMatch {
  name: string
  deep: boolean
}

/** Match `[page]` -> `{ name: 'page', deep: false }` or `[...page]` -> `{ name: 'page', deep: true }` */
export function matchDynamicName(name: string): DynamicNameMatch | undefined {
  const paramName = name.match(dynamicNameRe)?.[1]
  if (paramName == null) {
    return undefined
  } else if (paramName.startsWith('...')) {
    return { name: paramName.slice(3), deep: true }
  } else {
    return { name: paramName, deep: false }
  }
}

/**
 * Match a route pattern against a URL path, segment-by-segment, as a prefix.
 *
 * - Dynamic segments `[param]` match any single path segment
 * - Catch-all `[...param]` matches all remaining path segments
 * - Route groups like `(app)` in the pattern are skipped (they don't appear in URLs)
 * - A trailing `/index` in the pattern is stripped (index routes match their parent path)
 * - The pattern must match as a prefix of the path (leftover path segments are allowed)
 *
 * Returns `null` if the pattern doesn't match. Otherwise returns a specificity
 * score — higher means the pattern is more specific. Callers that want an
 * "exact" match (no leftover path) can check `result.specificity === pathSegmentsCount`.
 * Callers picking the best match among several patterns should pick the
 * highest specificity.
 *
 * Shared between:
 *  - `views/Navigator.tsx` — resolving initialRouteName for late-mounted navigators
 *  - `router/interceptRoutes.ts` — finding layouts that are ancestors of a path
 */
export function matchRoutePattern(
  pattern: string,
  path: string
): { specificity: number } | null {
  const patternSegments = pattern.split('/').filter(Boolean)
  // strip trailing `/index` — index routes match the parent path with nothing after
  if (patternSegments[patternSegments.length - 1] === 'index') {
    patternSegments.pop()
  }
  const pathSegments = path.split('/').filter(Boolean)

  let specificity = 0
  let pi = 0
  for (let ui = 0; ui < patternSegments.length; ui++) {
    const seg = patternSegments[ui]
    // route groups like (app) don't appear in URLs — skip them but don't count specificity
    if (seg.startsWith('(') && seg.endsWith(')')) continue
    // catch-all [...param] consumes the rest of the path
    if (seg.startsWith('[...') && seg.endsWith(']')) {
      // count remaining path segments so a catch-all beats a non-catch-all at the same depth
      return { specificity: specificity + (pathSegments.length - pi) }
    }
    // pattern has more segments than path → not a prefix match
    if (pi >= pathSegments.length) return null
    // dynamic [param] matches any single path segment (less specific than literal)
    if (seg.startsWith('[') && seg.endsWith(']')) {
      specificity += 1
      pi += 1
      continue
    }
    // literal segment must match exactly (most specific)
    if (seg !== pathSegments[pi]) return null
    specificity += 2
    pi += 1
  }
  // all pattern segments consumed — this is a valid prefix match
  return { specificity }
}

/**
 * Match `[...page]` -> `page`
 * @deprecated Use matchDynamicName instead which returns {name, deep}
 */
export function matchDeepDynamicRouteName(name: string): string | undefined {
  return name.match(/^\[\.\.\.([^/]+?)\]$/)?.[1]
}

/** Test `/` -> `page` */
export function testNotFound(name: string): boolean {
  return name.endsWith('+not-found')
}

/** Match `(page)` -> `page` */
export function matchGroupName(name: string): string | undefined {
  return name.match(/^(?:[^\\(\\)])*?\(([^\\/]+)\).*?$/)?.[1]
}

/** Match the first array group name `(a,b,c)/(d,c)` -> `'a,b,c'` */
export function matchArrayGroupName(name: string) {
  return name.match(/(?:[^\\(\\)])*?\(?([^\\/()]+,[^\\/()]+)\)?.*?$/)?.[1]
}

export function getNameFromFilePath(name: string): string {
  return removeSupportedExtensions(removeFileSystemDots(name))
}

export function getContextKey(name: string): string {
  // The root path is `` (empty string) so always prepend `/` to ensure
  // there is some value.
  const normal = '/' + getNameFromFilePath(name)
  if (!normal.endsWith('_layout')) {
    return normal
  }
  return normal.replace(/\/?_layout$/, '')
}

/** Remove `.js`, `.ts`, `.jsx`, `.tsx` */
export function removeSupportedExtensions(name: string): string {
  return name.replace(/(\+(api|spa|ssg|ssr))?\.[jt]sx?$/g, '')
}

// Remove any amount of `./` and `../` from the start of the string
export function removeFileSystemDots(filePath: string): string {
  return filePath.replace(/^(?:\.\.?\/)+/g, '')
}

export function stripGroupSegmentsFromPath(path: string): string {
  return path
    .split('/')
    .reduce((acc, v) => {
      if (matchGroupName(v) == null) {
        acc.push(v)
      }
      return acc
    }, [] as string[])
    .join('/')
}

export function stripInvisibleSegmentsFromPath(path: string): string {
  return stripGroupSegmentsFromPath(path).replace(/\/?index$/, '')
}

/**
 * Match:
 *  - _layout files, +html, +not-found, string+api, etc
 *  - Routes can still use `+`, but it cannot be in the last segment.
 *  - .d.ts files (type definition files)
 */
export function isTypedRoute(name: string) {
  return (
    !name.startsWith('+') &&
    !name.endsWith('.d.ts') &&
    name.match(/(_layout|[^/]*?\+[^/]*?)\.[tj]sx?$/) === null
  )
}

// ============================================
// Directory Render Modes
// ============================================

/** Match directory render mode suffixes: dashboard+ssr, blog+ssg, etc. */
const directoryRenderModeRe = /^(.+)\+(api|ssg|ssr|spa)$/

export interface DirectoryRenderModeMatch {
  /** Directory name without the render mode suffix */
  name: string
  /** The render mode for this directory */
  renderMode: One.RouteRenderMode | 'api'
}

/**
 * Match directory render mode suffixes
 *
 * Examples:
 *   - "dashboard+ssr" -> { name: "dashboard", renderMode: "ssr" }
 *   - "blog+ssg" -> { name: "blog", renderMode: "ssg" }
 *   - "admin+spa" -> { name: "admin", renderMode: "spa" }
 */
export function matchDirectoryRenderMode(
  name: string
): DirectoryRenderModeMatch | undefined {
  const match = name.match(directoryRenderModeRe)
  if (!match) return undefined
  return {
    name: match[1],
    renderMode: match[2] as One.RouteRenderMode | 'api',
  }
}

// ============================================
// Parallel Routes & Intercepting Routes
// ============================================

/** Match @slot directories: @modal, @sidebar, etc. */
const slotPrefixRe = /^@([a-zA-Z][a-zA-Z0-9_-]*)$/

/** Match @modal -> 'modal', @sidebar -> 'sidebar' */
export function matchSlotName(name: string): string | undefined {
  return name.match(slotPrefixRe)?.[1]
}

/** Check if a directory name is a slot directory */
export function isSlotDirectory(name: string): boolean {
  return slotPrefixRe.test(name)
}

export interface InterceptMatch {
  /** Number of levels up (0 = same level, 1 = parent, Infinity = root) */
  levels: number
  /** The actual route path after stripping intercept prefix */
  targetPath: string
  /** Original segment like "(.)photos" or "(..)photos" */
  originalSegment: string
}

/**
 * Match intercept prefixes: (.), (..), (...), (..)(..) etc.
 *
 * Examples:
 *   - "(.)photos" -> { levels: 0, targetPath: "photos" }
 *   - "(..)photos" -> { levels: 1, targetPath: "photos" }
 *   - "(...)photos" -> { levels: Infinity, targetPath: "photos" }
 *   - "(..)(..)photos" -> { levels: 2, targetPath: "photos" }
 */
export function matchInterceptPrefix(segment: string): InterceptMatch | undefined {
  // Match one or more intercept prefixes followed by the target path
  const match = segment.match(/^((?:\(\.{1,3}\))+)(.+)$/)
  if (!match) return undefined

  const [, prefixes, targetPath] = match

  // (...) means from root (Infinity levels)
  if (prefixes.includes('(...)')) {
    return { levels: Infinity, targetPath, originalSegment: segment }
  }

  // Count (..) for levels up, (.) means same level (0)
  const doubleDotMatches = prefixes.match(/\(\.{2}\)/g) || []
  const levels = doubleDotMatches.length

  return { levels, targetPath, originalSegment: segment }
}

/**
 * Strip intercept prefixes from a path segment
 * "(.)photos" -> "photos"
 * "(..)settings" -> "settings"
 */
export function stripInterceptPrefix(segment: string): string {
  const match = matchInterceptPrefix(segment)
  return match ? match.targetPath : segment
}

/**
 * Check if a segment has an intercept prefix
 */
export function hasInterceptPrefix(segment: string): boolean {
  return /^\(\.{1,3}\)/.test(segment)
}

/**
 * Strip slot prefix from path for URL generation
 * Removes @slot segments from path
 */
export function stripSlotSegmentsFromPath(path: string): string {
  return path
    .split('/')
    .filter((segment) => !isSlotDirectory(segment))
    .join('/')
}
