/**
 * This is a utility script that converts the YAML here into ProtoDef schema code
 * You can run this with `npm run build`
 */
const fs = require('fs')
const { join } = require('path')
const { parse, compile } = require('protodef-yaml')

function getJSON (path) {
  return JSON.parse(fs.readFileSync(path, 'utf-8'))
}

// Parse the YML files and turn to JSON
function genProtoSchema (createPacketMap) {
  // Create the packet_map.yml from proto.yml
  const parsed = parse('./proto.yml')
  const version = parsed['!version']

  if (createPacketMap) {
    // On bedrock the packet maps are created automatically here
    const packets = []
    for (const key in parsed) {
      if (key.startsWith('%container')) {
        const [, name] = key.split(',')
        if (name.startsWith('packet_')) {
          const children = parsed[key]
          const packetName = name.replace('packet_', '')
          const packetID = children['!id']
          packets.push([packetID, packetName, name])
        }
      }
    }
    let l1 = ''
    let l2 = ''
    for (const [id, name, fname] of packets) {
      l1 += `      0x${id.toString(16).padStart(2, '0')}: ${name}\n`
      l2 += `      if ${name}: ${fname}\n`
    }
    // TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly
    const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yml\nmcpe_packet:\n   name: varint =>\n${l1}\n   params: name ?\n${l2}`
    fs.writeFileSync('./packet_map.yml', t)
  }

  const json = compile('./proto.yml')
  fs.rmSync('./packet_map.yml', { force: true }) // temp file
  return [version, json]
}

function visitor (key, value) {
  // Convert decimal to hex in protocol.json for packet mapper fields
  if ((key === 'packet') && (value?.[1]?.[0]?.name === 'name')) {
    const mapper = value[1][0].type[1].mappings
    for (const key in mapper) {
      if (key.startsWith('0x')) continue
      mapper['0x' + Number(key).toString(16).padStart(2, '0')] = mapper[key]
      delete mapper[key]
    }
  }
  return value
}

function convert (edition, ver, path) {
  process.chdir(path || join(__dirname, `../../data/${edition}/${ver}`))
  const [version, json] = genProtoSchema(edition === 'bedrock')
  fs.mkdirSync(`../${version}`, { recursive: true })
  if (edition === 'bedrock') {
    fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: json }, visitor, 2))
  } else if (edition === 'pc') {
    fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify(json, visitor, 2))
  }
  return version
}

function validate (edition, ver, path) {
  process.chdir(path || join(__dirname, `../../data/${edition}/${ver}`))
  // console.log(process.cwd())
  const [version, json] = genProtoSchema(edition === 'bedrock')

  if (path.endsWith('latest')) {
    if (fs.existsSync(join(__dirname, `../../data/${edition}/${version}/proto.yml`))) {
      throw Error(`Both ${edition}/latest/proto.yml and ${edition}/${version}/proto.yml exist for ${version}, please remove the latter.`)
    }
  }

  const expected = edition === 'bedrock'
    ? JSON.stringify({ types: json }, visitor, 2)
    : JSON.stringify(json, visitor, 2)

  // If you crash here, no protocol.json was generated - run `npm run build`
  const actual = JSON.stringify(getJSON(`../${version}/protocol.json`), null, 2)

  // Make sure the protocol_expected.json file equals the protocol.json file; otherwise the JSON must be rebuilt
  if (expected !== actual) {
    // console.log(expected.length, actual.length, expected, actual)
    throw Error(`${ver} (${version}) / protocol.json is desynced from yaml, please run 'npm run build'`)
  }

  // console.log('ok', `../${version}/protocol.json`)
  return version
}

const dataPaths = require('../../data/dataPaths.json')
function all (fn) {
  for (const edition in dataPaths) {
    const versions = dataPaths[edition]
    for (const versionId in versions) {
      console.log('⏳', fn.name, 'protocol for', edition, versionId)
      const paths = versions[versionId]
      if (paths.proto) {
        fn(edition, versionId, join(__dirname, '../../data/' + paths.proto))
      }
    }
  }
}

// If no argument, build everything
if (!module.parent) {
  if (!process.argv[2]) {
    all(convert)
  } else { // build the specified version
    convert(process.argv[2], process.argv[3])
  }
}

module.exports = { convert, validate, all }
