import { type Context } from 'hono'
import { stat } from 'fs/promises'

// template literal tag for inline CSS. returns a <style> tag
export function css(strings: TemplateStringsArray, ...values: any[]) {
  return <style dangerouslySetInnerHTML={{
    __html: String.raw({ raw: strings }, ...values)
  }} />
}

const transpiler = new Bun.Transpiler({ loader: 'tsx' })

// template literal tag for inline JS. transpiles and returns a <script> tag
export function js(strings: TemplateStringsArray, ...values: any[]) {
  return <script dangerouslySetInnerHTML={{
    __html: transpiler.transformSync(String.raw({ raw: strings }, ...values))
  }} />
}

// lighten a hex color by blending with white. opacity 1 = original, 0 = white
export function lightenColor(hex: string, opacity: number): string {
  // Remove # if present
  hex = hex.replace('#', '')

  // Convert to RGB
  let r = parseInt(hex.substring(0, 2), 16)
  let g = parseInt(hex.substring(2, 4), 16)
  let b = parseInt(hex.substring(4, 6), 16)

  // Blend with white
  r = Math.round(r * opacity + 255 * (1 - opacity))
  g = Math.round(g * opacity + 255 * (1 - opacity))
  b = Math.round(b * opacity + 255 * (1 - opacity))

  // Convert back to hex
  return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')
}

// darken a hex color by blending with black. opacity 1 = original, 0 = black
export function darkenColor(hex: string, opacity: number): string {
  hex = hex.replace('#', '')

  let r = parseInt(hex.substring(0, 2), 16)
  let g = parseInt(hex.substring(2, 4), 16)
  let b = parseInt(hex.substring(4, 6), 16)

  // Blend with black (0, 0, 0)
  r = Math.round(r * opacity)
  g = Math.round(g * opacity)
  b = Math.round(b * opacity)

  return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')
}

// check if the user prefers dark mode
export function isDarkMode(): boolean {
  return window.matchMedia('(prefers-color-scheme: dark)').matches
}

// capitalize a word. that's it.
export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

// Generate a 6 character random ID
export function randomId(): string {
  return Math.random().toString(36).slice(7)
}

// times(3) returns [1, 2, 3]
export function times(n: number): number[] {
  let out = [], i = 1
  while (i <= n) out.push(i++)
  return out
}

// inclusive
// rand(2) == flip a coin
// rand(6) == roll a die
// rand(20) == dnd
export function rand(end = 2, startAtZero = false): number {
  const start = startAtZero ? 0 : 1
  return Math.floor(Math.random() * (end - start + 1)) + start
}

// randRange(1, 2) == flip a coin
// randRange(1, 6) == roll a die
// randRange(1, 20) == dnd
export function randRange(start = 0, end = 12): number {
  return Math.floor(Math.random() * (end - start + 1)) + start
}

// randomItem([5, 7, 9]) #=> 7
export function randItem<T>(list: T[]): T | undefined {
  if (list.length === 0) return
  return list[randRange(0, list.length - 1)]
}

// randomIndex([5, 7, 9]) #=> 1
export function randIndex<T>(list: T[]): number | undefined {
  if (!list.length) return
  return randRange(0, list.length - 1)
}

// unique([1,1,2,2,3,3]) #=> [1,2,3]
export function unique<T>(array: T[]): T[] {
  return [...new Set(array)]
}

// shuffle a copy of an array
export function shuffle<T>(arr: readonly T[]): T[] {
  const out = arr.slice()
  for (let i = out.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))

    const tmp = out[i]!
    out[i] = out[j]!
    out[j] = tmp
  }
  return out
}

// random number between 1 and 10, with decreasing probability
export function weightedRand(): number {
  // Weights: 1 has weight 10, 2 has weight 9, ..., 10 has weight 1
  const weights = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
  const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0)
  let random = Math.random() * totalWeight

  for (let i = 0; i < weights.length; i++) {
    random -= weights[i]!
    if (random <= 0) {
      return numbers[i]!
    }
  }

  return numbers[numbers.length - 1]!
}

const transpileCache: Record<string, string> = {}

// transpile frontend ts to js
export async function transpile(path: string): Promise<string> {
  const { mtime } = await stat(path)
  const key = `${path}?${mtime}`

  let cached = process.env.NODE_ENV === 'production' ? transpileCache[key] : undefined

  if (!cached) {
    const result = await Bun.build({
      entrypoints: [path],
      format: 'esm',
      minify: false,
      sourcemap: process.env.NODE_ENV === 'production' ? 'none' : 'inline',
    })

    if (!result.outputs[0]) throw new Error(`Failed to build ${path}`)

    cached = await result.outputs[0].text()
    transpileCache[key] = cached
  }

  return cached
}

// redirect to the referrer, or fallback if no referrer
export function redirectBack(c: Context, fallback = "/") {
  return c.redirect(c.req.header("Referer") || fallback)
}
