// This file generates "internal/compat/js_table.go"

import fs = require('fs')
import { Engine, JSFeature, VersionRange, VersionRangeMap, WhyNotMap, engines } from './index'

const jsFeatureString = (feature: string): string => {
  return feature.replace(/([A-Z])/g, '-$1').slice(1).toLowerCase()
}

const simpleMap = (entries: [string, string][]) => {
  let maxLength = 0
  for (const [key] of entries) {
    maxLength = Math.max(maxLength, key.length + 1)
  }
  return entries.map(([key, value]) => `\t${(key + ':').padEnd(maxLength)} ${value},`).join('\n')
}

const compareEngines = (a: Engine, b: Engine): number => {
  const lowerA = a.toLowerCase()
  const lowerB = b.toLowerCase()
  return lowerA < lowerB ? -1 : lowerA > lowerB ? 1 : 0
}

const jsTableMap = (map: Partial<Record<Engine, VersionRange[]>>, whyNot: Partial<Record<Engine, string[]>>) => {
  const lines: string[] = []
  const whyNotKeys = (Object.keys(whyNot) as Engine[]).sort(compareEngines)
  for (const engine of whyNotKeys) {
    const failedTests = whyNot[engine]!
    lines.push(`\t\t// Note: The latest version of ${JSON.stringify(engine)} failed ` + (failedTests.length === 1
      ? `this test: ${failedTests[0]}` : `${failedTests.length} tests including: ${failedTests[0]}`))
  }

  const engineKeys = (Object.keys(map) as Engine[]).sort(compareEngines)
  const maxLength = engineKeys.reduce((a, b) => Math.max(a, b.length + 1), 0)
  for (const engine of engineKeys) {
    const items = map[engine]!.map(range => {
      return `{start: v{${range.start.concat(0, 0).slice(0, 3).join(', ')
        }}${range.end ? `, end: v{${range.end.concat(0, 0).slice(0, 3).join(', ')}}` : ''}}`
    })
    lines.push(`\t\t${(engine + ':').padEnd(maxLength)} {${items.join(', ')}},`)
  }

  if (lines.length === 0) return '{}'
  return `{\n${lines.join('\n')}\n\t}`
}

const jsTableValidEnginesMap = (engines: Engine[]) => {
  const maxLength = engines.reduce((a, b) => Math.max(a, b.length + 4), 0)
  if (engines.length === 0) return '{}'
  return engines.map(engine => {
    return `\t${`"${engine.toLowerCase()}": `.padEnd(maxLength)}api.Engine${engine},`
  }).join('\n')
}

const generatedByComment = `// This file was automatically generated by "js_table.ts"`

export const generateTableForJS = (map: VersionRangeMap<JSFeature>, whyNot: WhyNotMap<JSFeature>): void => {
  const enginesKeys = (Object.keys(engines) as Engine[]).sort(compareEngines)

  fs.writeFileSync(__dirname + '/../internal/compat/js_table.go',
    `${generatedByComment}

package compat

type Engine uint8

const (
${enginesKeys.map((engine, i) => `\t${engine}${i ? '' : ' Engine = iota'}`).join('\n')}
)

func (e Engine) String() string {
\tswitch e {
${enginesKeys.map(engine => `\tcase ${engine}:\n\t\treturn "${engine.toLowerCase()}"`).join('\n')}
\t}
\treturn ""
}

func (e Engine) IsBrowser() bool {
\tswitch e {
\tcase Chrome, Edge, Firefox, IE, IOS, Opera, Safari:
\t\treturn true
\t}
\treturn false
}

type JSFeature uint64

const (
${Object.keys(map).sort().map((feature, i) => `\t${feature}${i ? '' : ' JSFeature = 1 << iota'}`).join('\n')}
)

var StringToJSFeature = map[string]JSFeature{
${simpleMap(Object.keys(map).sort().map(feature => [`"${jsFeatureString(feature)}"`, feature]))}
}

func (features JSFeature) Has(feature JSFeature) bool {
\treturn (features & feature) != 0
}

func (features JSFeature) ApplyOverrides(overrides JSFeature, mask JSFeature) JSFeature {
\treturn (features & ^mask) | (overrides & mask)
}

var jsTable = map[JSFeature]map[Engine][]versionRange{
${Object.keys(map).sort().map(feature => `\t${feature}: ${jsTableMap(map[feature as JSFeature]!, whyNot[feature as JSFeature]!)},`).join('\n')}
}

// Return all features that are not available in at least one environment
func UnsupportedJSFeatures(constraints map[Engine]Semver) (unsupported JSFeature) {
\tfor feature, engines := range jsTable {
\t\tif feature == InlineScript {
\t\t\tcontinue // This is purely user-specified
\t\t}
\t\tfor engine, version := range constraints {
\t\t\tif versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
\t\t\t\tunsupported |= feature
\t\t\t}
\t\t}
\t}
\treturn
}
`)

  fs.writeFileSync(__dirname + '/../pkg/api/api_js_table.go',
    `${generatedByComment}

package api

import "github.com/evanw/esbuild/internal/compat"

type EngineName uint8

const (
${enginesKeys.filter(engine => engine !== 'ES').map((engine, i) => `\tEngine${engine}${i ? '' : ' EngineName = iota'}`).join('\n')}
)

func convertEngineName(engine EngineName) compat.Engine {
\tswitch engine {
${enginesKeys.filter(engine => engine !== 'ES').map(engine => `\tcase Engine${engine}:\n\t\treturn compat.${engine}`).join('\n')}
\tdefault:
\t\tpanic("Invalid engine name")
\t}
}
`)

  fs.writeFileSync(__dirname + '/../pkg/cli/cli_js_table.go',
    `${generatedByComment}

package cli

import "github.com/evanw/esbuild/pkg/api"

var validEngines = map[string]api.EngineName{
${jsTableValidEnginesMap(enginesKeys.filter(engine => engine !== 'ES'))}
}
`)
}
