/* eslint no-console: 0 */

import readline from 'node:readline'
import colors from 'picocolors'
import type { RollupError } from 'rolldown'
import type { ResolvedServerUrls } from './server'

export type LogType = 'error' | 'warn' | 'info'
export type LogLevel = LogType | 'silent'
export interface Logger {
  info(msg: string, options?: LogOptions): void
  warn(msg: string, options?: LogOptions): void
  warnOnce(msg: string, options?: LogOptions): void
  error(msg: string, options?: LogErrorOptions): void
  clearScreen(type: LogType): void
  hasErrorLogged(error: Error | RollupError): boolean
  hasWarned: boolean
}

export interface LogOptions {
  clear?: boolean
  timestamp?: boolean
  environment?: string
}

export interface LogErrorOptions extends LogOptions {
  error?: Error | RollupError | null
}

export const LogLevels: Record<LogLevel, number> = {
  silent: 0,
  error: 1,
  warn: 2,
  info: 3,
}

let lastType: LogType | undefined
let lastMsg: string | undefined
let sameCount = 0

function clearScreen() {
  const repeatCount = process.stdout.rows - 2
  const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''
  console.log(blank)
  readline.cursorTo(process.stdout, 0, 0)
  readline.clearScreenDown(process.stdout)
}

export interface LoggerOptions {
  prefix?: string
  allowClearScreen?: boolean
  customLogger?: Logger
  console?: Console
}

// Only initialize the timeFormatter when the timestamp option is used, and
// reuse it across all loggers
let timeFormatter: Intl.DateTimeFormat
function getTimeFormatter() {
  timeFormatter ??= new Intl.DateTimeFormat(undefined, {
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
  })
  return timeFormatter
}

export function createLogger(
  level: LogLevel = 'info',
  options: LoggerOptions = {},
): Logger {
  if (options.customLogger) {
    return options.customLogger
  }

  const loggedErrors = new WeakSet<Error | RollupError>()
  const {
    prefix = '[vite]',
    allowClearScreen = true,
    console = globalThis.console,
  } = options
  const thresh = LogLevels[level]
  const canClearScreen =
    allowClearScreen && process.stdout.isTTY && !process.env.CI
  const clear = canClearScreen ? clearScreen : () => {}

  function format(type: LogType, msg: string, options: LogErrorOptions = {}) {
    if (options.timestamp) {
      let tag = ''
      if (type === 'info') {
        tag = colors.cyan(colors.bold(prefix))
      } else if (type === 'warn') {
        tag = colors.yellow(colors.bold(prefix))
      } else {
        tag = colors.red(colors.bold(prefix))
      }
      const environment = options.environment ? options.environment + ' ' : ''
      return `${colors.dim(getTimeFormatter().format(new Date()))} ${tag} ${environment}${msg}`
    } else {
      return msg
    }
  }

  function output(type: LogType, msg: string, options: LogErrorOptions = {}) {
    if (thresh >= LogLevels[type]) {
      const method = type === 'info' ? 'log' : type

      if (options.error) {
        loggedErrors.add(options.error)
      }
      if (canClearScreen) {
        if (type === lastType && msg === lastMsg) {
          sameCount++
          clear()
          console[method](
            format(type, msg, options),
            colors.yellow(`(x${sameCount + 1})`),
          )
        } else {
          sameCount = 0
          lastMsg = msg
          lastType = type
          if (options.clear) {
            clear()
          }
          console[method](format(type, msg, options))
        }
      } else {
        console[method](format(type, msg, options))
      }
    }
  }

  const warnedMessages = new Set<string>()

  const logger: Logger = {
    hasWarned: false,
    info(msg, opts) {
      output('info', msg, opts)
    },
    warn(msg, opts) {
      logger.hasWarned = true
      output('warn', msg, opts)
    },
    warnOnce(msg, opts) {
      if (warnedMessages.has(msg)) return
      logger.hasWarned = true
      output('warn', msg, opts)
      warnedMessages.add(msg)
    },
    error(msg, opts) {
      logger.hasWarned = true
      output('error', msg, opts)
    },
    clearScreen(type) {
      if (thresh >= LogLevels[type]) {
        clear()
      }
    },
    hasErrorLogged(error) {
      return loggedErrors.has(error)
    },
  }

  return logger
}

const maxNetworkInterfaceNameLength = 20

export function printServerUrls(
  urls: ResolvedServerUrls,
  optionsHost: string | boolean | undefined,
  info: Logger['info'],
): void {
  const colorUrl = (url: string) =>
    colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`))
  for (const url of urls.local) {
    info(`  ${colors.green('➜')}  ${colors.bold('Local')}:   ${colorUrl(url)}`)
  }
  const networkUrlMaxLength = Math.max(
    ...urls.network.map((url) => url.length),
    0,
  )
  urls.network.forEach((url, index) => {
    const interfaceName = urls.networkInterfaceNames?.[index]
    let suffix = ''
    if (interfaceName) {
      const label =
        interfaceName.length > maxNetworkInterfaceNameLength
          ? `${interfaceName.slice(0, maxNetworkInterfaceNameLength - 1)}…`
          : interfaceName
      suffix =
        ' '.repeat(networkUrlMaxLength - url.length + 2) + colors.dim(label)
    }
    info(
      `  ${colors.green('➜')}  ${colors.bold('Network')}: ${colorUrl(url)}${suffix}`,
    )
  })
  if (urls.network.length === 0 && optionsHost === undefined) {
    info(
      colors.dim(`  ${colors.green('➜')}  ${colors.bold('Network')}: use `) +
        colors.bold('--host') +
        colors.dim(' to expose'),
    )
  }
}


// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.

// Simple test for a fuzzer. The fuzzer must find the interesting switch value.
#include <cstdint>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstddef>

int Switch(int a) {
  switch(a) {
    case 100001: return 1;
    case 100002: return 2;
    case 100003: return 4;
  }
  return 0;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  const int N = 3;
  if (Size < N * sizeof(int)) return 0;
  int Res = 0;
  for (int i = 0; i < N; i++) {
    int X;
    memcpy(&X, Data + i * sizeof(int), sizeof(int));
    Res += Switch(X);
  }
  if (Res == 5 || Res == 3 || Res == 6 || Res == 7) {
    fprintf(stderr, "BINGO; Found the target, exiting; Res=%d\n", Res);
    exit(1);
  }
  return 0;
}


let state = {}

export const render = (newState) => {
  state = newState
  apply()
}

export const rerender = (updates) => {
  state = { ...state, ...updates }
  apply()
}

const apply = () => {
  globalThis.__HMR__['.file-delete-restore'] = Object.values(state).join(':')
}


import { value } from './child'

if (import.meta.hot) {
  import.meta.hot.accept(() => {})
}

console.log('(invalidation circular deps handled) parent is executing')
setTimeout(() => {
  document.querySelector('.invalidation-circular-deps-handled').innerHTML =
    value
})


import { expect, test } from 'vitest'
import { getBg, isBundled, page, viteTestUrl } from '~utils'
import './tests'

// not included in tests.ts because the lightningcss variant does not use
// the postcss pipeline
test('postcss plugin that injects url() at OnceExit', async () => {
  await page.goto(viteTestUrl)
  const imported = await page.waitForSelector('.inject-url-once-exit')
  // url should be rebased against the injected source file
  expect(await getBg(imported)).toMatch(
    isBundled ? /base64/ : '/injected-source/injected-bg.png',
  )
})