const sleep = x => new Promise(res => setTimeout(() => res(), x * 1000))

;(async () => {
    const { spawn } = require('node:child_process')
    const ora = (await import('ora')).default

    class Builder {
        constructor() {
            this.spinner = ora('Starting build').start()
            this.contextMaxLines = 3
            this.contextLines = Array(this.contextMaxLines).fill('')
            this.allLines = []
            this.steps = []
            this.renderLines()
        }

        pushLine(line) {
            if ( this.contextLines.length >= this.contextMaxLines ) this.contextLines = this.contextLines.slice(1)
            this.contextLines.push(String(line).trimEnd())
            this.allLines.push(String(line).trimEnd())
            this.renderLines()
        }

        renderLines() {
            this.spinner.suffixText = `\n--\n${this.contextLines.join('\n')}\n--`
        }

        addStep(name, callback) {
            this.steps.push({ name, callback })
        }

        async build() {
            for ( let i = 0; i < this.steps.length; i += 1 ) {
                const step = this.steps[i]
                this.spinner.text = `(${i+1}/${this.steps.length}) ${step.name}`
                await step.callback(this)
            }

            this.spinner.suffixText = ''
            this.spinner.succeed('Built successfully')
        }

        async executeOrExit(prog, ...args) {
            try {
                return await this.execute(prog, ...args)
            } catch (e) {
                this.spinner.suffixText = ''
                this.spinner.fail(`${this.spinner.text} - ${e.message}`)
                console.log(this.allLines.join('\n'))
                process.exit(1)
            }
        }

        execute(prog, ...args) {
            this.pushLine(`> ${prog} ${args.join(' ')}`)

            const cmd = spawn(prog, args)
            let output = ''

            cmd.stdout.on('data', data => {
                output += data
                String(data).split('\n').map(x => this.pushLine(x))
            })
            cmd.stderr.on('data', data => {
                output += data
                String(data).split('\n').map(x => this.pushLine(x))
            })

            return new Promise((res, rej) => {
                cmd.on('close', code => {
                    if ( code ) {
                        return rej(new Error('Process exited with code: ' + code))
                    } else {
                        res(output.trim())
                    }
                })
            })
        }
    }

    const b= new Builder

    b.addStep(
        'Remove old build files',
        b => b.executeOrExit('./node_modules/.bin/rimraf', 'lib'),
    )

    b.addStep(
        'Build back-end TypeScript code',
        b => b.executeOrExit('./node_modules/.bin/tsc', '-p', 'tsconfig.node.json'),
    )

    b.addStep(
        'Copy resources to output bundle',
        b => b.executeOrExit('./node_modules/.bin/fse', 'copy', '--all', '--dereference', '--preserveTimestamps', '--keepExisting=false', '--quiet', '--errorOnExist=false', 'src/app/resources', 'lib/app/resources'),
    )

    b.addStep(
        'Build front-end TypeScript code',
        b => b.executeOrExit('./node_modules/.bin/tsc', '-p', 'tsconfig.client.json'),
    )

    b.addStep(
        'Create front-end output bundle',
        b => b.executeOrExit('./node_modules/.bin/webpack', '--config', 'webpack.config.js'),
    )

    await b.build()
})();
