import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import { formatBytes } from '@ocavue/utils'
import type * as SWC from '@swc/html'
import type { AstroIntegration, AstroIntegrationLogger } from 'astro'
import glob from 'fast-glob'

type SizeChange = [sizeBefore: number, sizeAfter: number]

function formatSizeChange([sizeBefore, sizeAfter]: SizeChange): string {
  const diff = sizeAfter - sizeBefore
  const percent = (100 * diff) / sizeBefore
  return `${formatBytes(diff)} (${percent.toFixed(1)}%)`
}

async function compress(
  logger: AstroIntegrationLogger,
  minify: typeof SWC.minify,
  dirPath: string,
  filePath: string,
  isDebug: boolean,
): Promise<SizeChange> {
  try {
    const fullPath = path.join(dirPath, filePath)
    const textBefore = await fs.readFile(fullPath, 'utf-8')
    const sizeBefore = Buffer.byteLength(textBefore, 'utf-8')

    const result = await minify(textBefore, {
      collapseWhitespaces: 'conservative',
      removeComments: true,
      minifyJson: true,
      minifyJs: true,
      minifyCss: true,
    })

    const textAfter = result.code
    const sizeAfter = Buffer.byteLength(textAfter, 'utf-8')

    await fs.writeFile(fullPath, textAfter, 'utf-8')

    if (isDebug) {
      logger.debug(`${formatSizeChange([sizeBefore, sizeAfter])} ${filePath}`)
    }

    return [sizeBefore, sizeAfter]
  } catch (error) {
    logger.error(`Failed to minify ${filePath}:`)
    logger.error(String(error))
    return [0, 0]
  }
}

export default function integration(): AstroIntegration {
  return {
    name: 'astro-minify-html-swc',
    hooks: {
      'astro:build:done': async ({ logger, dir }) => {
        const timeStart = performance.now()

        const dirPath = fileURLToPath(dir)
        logger.debug(`Scanning ${dirPath}`)

        const filePaths = await glob('**/*.html', { cwd: dirPath })
        logger.debug(`Found ${filePaths.length} HTML files in ${dirPath}`)

        const { minify } = await import('@swc/html')

        const isDebug = logger.options.level === 'debug'

        const sizeChanges = await Promise.all(
          filePaths.map((filePath) => {
            return compress(logger, minify, dirPath, filePath, isDebug)
          }),
        )

        const totalSizeChange: SizeChange = [0, 0]
        for (const sizeChange of sizeChanges) {
          totalSizeChange[0] += sizeChange[0]
          totalSizeChange[1] += sizeChange[1]
        }

        const timeEnd = performance.now()
        logger.info(
          `${formatSizeChange(totalSizeChange)} Compressed ${filePaths.length} HTML files in ${Math.round(timeEnd - timeStart)}ms`,
        )
      },
    },
  } satisfies AstroIntegration
}
