#!/bin/zsh

setopt extended_glob
setopt glob_star_short

local script="${0}"

errf() {
  >&2 printf "${@}"
}

local errored="no"
local invalid_options=()
log_invalidf() {
  errored="yes"
  invalid_options+=(${2})
  errf "${@}"
}

tracef() {
  [[ -n ${TRACE} ]] && errf "TRACE: ${@}"
}

expect_directory() {
  option=${1}
  value="${2}"
  expect_value ${option} "${value}" && if [[ ! -d "${value}" ]]; then
    log_invalidf 'WARN: option: %s: requires directory, got: %s\n' ${option} "${value}"
    errf         '      file %s\n' "$(file ${value})"
    errf         'WARN: ignoring %s option and continuing.\n' ${option}
    return 1
  fi
}

expect_value() {
  option=${1}
  if [[ ${#} -lt 1 ]]; then
    log_invalidf 'option: unexpected end of flags: no value given for: %s\n' ${option}
    continue
  fi
  value="${2}"
  if [[ "${value}" =~ -- ]]; then
    log_invalidf 'option: %s requires value, got other option: %s\n' ${option} ${value}
    return 1
  fi
  if [[ -z "${value}" ]]; then
    log_invalidf 'ERROR: option: value is empty for: %s\n' ${option}
    shift 1
    return 1
  fi
}

generate_build_script() {
  local sources
  local strip_prefix
  local outdir

  while [[ ${1} =~ -- ]]; do
    option=${1}
    shift 1
    case ${option} in
      "--strip-prefix")
        if expect_value ${option} "${1}"; then
          tracef 'option: strip-prefix="%s"\n' "${1}"
          strip_prefix="${1}"
        fi
        shift 1
        ;;
      "--sources")
        if expect_value ${option} "${1}"; then
          tracef 'option: sources=(%s)\n' "${1}"
          sources=(${~1})
        fi
        shift 1
        ;;
      "--outdir")
        mkdir --parents "${1}"
        if expect_directory ${option} "${1}"; then
          tracef 'option: outdir="%s"\n' "${1}"
          outdir="${1}"
        fi
        shift 1
        ;;
      *)
        log_invalidf 'ERROR: option: unrecognized option: %s\n' ${option}
        if [[ -n "${1}" && ! (${1} =~ --) ]]; then
          errf 'ERROR: option: discarding likely argument of unrecognized option (%s): "%s"\n' ${option} "${1}"
          shift 1
        fi
        ;;
    esac
  done

  if [[ ${#invalid_options} -gt 0 ]]; then
    errf 'WARN: some option were invalid: (%s)\n' "${invalid_options[*]}"
  fi
  if [[ ${errored} == "yes" ]]; then
    errf 'WARN: some error(s) occurred parsing options, see above.\n'
    return 1
  fi

  local sources_json="[\"${(j:",":)sources}\"]"
  cat > .build.tmp.mjs <<EOF
    import * as fs   from 'node:fs/promises'
    import * as path from 'node:path'
    import pug       from 'pug'

    function ms_time_diff(start, end) {
      const us = Number(BigInt.asUintN(64, (end - start) / 1000n))
      return us / 1000
    }

    function format_ms(number) {
      return number.toFixed(3).toString().padStart(8, ' ')
    }

    const sources = ${sources_json}
    const longest_source_length = Math.max.apply(Math, sources.map(s => s.length))
    const strip_prefix = "${strip_prefix}"

    const total_start = process.hrtime.bigint()
    for (const source of sources) {
      const source_directory = path.dirname(source)
      const source_basename  = path.basename(source, ".pug")
      const source_base_index = source_directory.indexOf(strip_prefix)
      const source_base_path = source_directory.substring(source_base_index + strip_prefix.length)
      const output_directory = path.join("${outdir}", source_base_path)
      const output_file_name = \`\${source_basename}.html\`
      const output_file = path.join(output_directory, output_file_name)
      const start = process.hrtime.bigint()
      const html = pug.compileFile(source)({ /* locals */ })
      await fs.mkdir(output_directory, { recursive: true })
      await fs.writeFile(output_file, html)
      const end = process.hrtime.bigint()
      console.log(''
        + \`+ \${format_ms(ms_time_diff(start, end))}ms\`
        + \`  \${source.padEnd(longest_source_length, ' ')}\`
        + \`\${output_file}\`
      )
    }
    const total_end = process.hrtime.bigint()
    const total_diff = ms_time_diff(total_start, total_end)
    console.log(\`σ \${format_ms(total_diff)}ms (internal) total compile time\`)
    console.log(\`Σ \${format_ms(process.uptime() * 1000)}ms (process)  total lifetime\`)
EOF
}

errf 'generating build script...\n'
TIMEFMT="TIME: %P cpu for %mE: %J"
time (generate_build_script "${@}")
if [[ $? -ne 0 ]]; then
  errf 'FATAL: failed to generate build script!\n'
  exit 1
fi
errf 'invoking generated build script...\n'
time node .build.tmp.mjs
rm .build.tmp.mjs