/**
 * Minimal self-contained static SPA server (no @fastify/static dependency). Serves the esbuild
 * output from `root` (control-panel/public/build) with a small content-type map, path-traversal
 * guard, and an index.html fallback for extension-less GETs (SPA routing). Registered LAST so the
 * specific /login and /api/* routes always win in Fastify's router.
 */
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { resolve, sep, extname } from 'node:path'
import type { FastifyPluginAsync, FastifyReply } from 'fastify'

const CONTENT_TYPES: Readonly<Record<string, string>> = {
  '.html': 'text/html; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
  '.mjs': 'text/javascript; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.map': 'application/json; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.png': 'image/png',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
  '.webmanifest': 'application/manifest+json',
}

async function isFile(path: string): Promise<boolean> {
  try {
    return (await stat(path)).isFile()
  } catch {
    return false
  }
}

async function sendFile(reply: FastifyReply, filePath: string): Promise<void> {
  const type = CONTENT_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream'
  await reply.type(type).send(createReadStream(filePath))
}

export function buildStaticRoutes(root: string): FastifyPluginAsync {
  const rootResolved = resolve(root)
  const indexHtml = resolve(rootResolved, 'index.html')

  return async (app) => {
    app.get('/*', async (req, reply) => {
      // Only GET/HEAD reach here (Fastify method routing); resolve the URL path safely.
      const urlPath = decodeURIComponent(req.url.split('?')[0] ?? '/')
      const candidate = resolve(rootResolved, '.' + urlPath)

      // Path-traversal guard: the resolved target must stay within root.
      if (candidate !== rootResolved && !candidate.startsWith(rootResolved + sep)) {
        return reply.code(403).send({ error: 'forbidden' })
      }

      if (urlPath !== '/' && (await isFile(candidate))) {
        return sendFile(reply, candidate)
      }
      // SPA fallback: serve index.html for '/' and any unknown extension-less route.
      if (await isFile(indexHtml)) {
        return sendFile(reply, indexHtml)
      }
      return reply.code(404).send({ error: 'not found' })
    })
  }
}
