// Package uploadpolicy contains the stateless filename and content policy
// shared by upload handling and local file serving.
package uploadpolicy

import (
	"errors"
	"path/filepath"
	"strings"
)

var (
	allowedExtensions = map[string]struct{}{
		".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".webp": {}, ".bmp": {}, ".ico": {}, ".avif": {},
		".mp3": {}, ".wav": {}, ".ogg": {}, ".m4a": {}, ".flac": {}, ".aac": {},
		".mp4": {}, ".webm": {}, ".mov": {}, ".avi": {}, ".mkv": {},
		".txt": {}, ".md": {}, ".csv": {}, ".json": {}, ".log": {}, ".pdf": {},
		".doc": {}, ".docx": {}, ".xls": {}, ".xlsx": {}, ".ppt": {}, ".pptx": {},
		".zip": {}, ".rar": {}, ".7z": {}, ".tar": {}, ".gz": {}, ".tgz": {}, ".bin": {},
	}

	inlineExtensions = map[string]struct{}{
		".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".webp": {}, ".bmp": {}, ".ico": {}, ".avif": {},
		".mp3": {}, ".wav": {}, ".ogg": {}, ".m4a": {}, ".flac": {}, ".aac": {},
		".mp4": {}, ".webm": {}, ".mov": {}, ".avi": {}, ".mkv": {},
	}
)

var errFileExtensionNotAllowed = errors.New("file extension is not allowed")

// ValidateFileName rejects paths, surrounding whitespace, and extensions
// outside the upload allowlist.
func ValidateFileName(filename string) error {
	if filename == "" || strings.TrimSpace(filename) != filename || strings.ContainsAny(filename, `/\`) {
		return errFileExtensionNotAllowed
	}
	if _, ok := allowedExtensions[strings.ToLower(filepath.Ext(filename))]; !ok {
		return errFileExtensionNotAllowed
	}
	return nil
}

// CanServeInline reports whether a stored object may be rendered by a
// browser instead of being forced to download.
func CanServeInline(filename string) bool {
	if ValidateFileName(filename) != nil {
		return false
	}
	_, ok := inlineExtensions[strings.ToLower(filepath.Ext(filename))]
	return ok
}

// MIMEByExtension returns the small extension fallback table used when a
// client did not provide a content type. Unknown extensions are left for
// content sniffing by the caller.
func MIMEByExtension(ext string) string {
	switch strings.ToLower(ext) {
	case ".json":
		return "application/json"
	case ".xml":
		return "application/xml"
	case ".pdf":
		return "application/pdf"
	case ".zip":
		return "application/zip"
	case ".mp4":
		return "video/mp4"
	case ".mp3":
		return "audio/mpeg"
	case ".csv":
		return "text/csv"
	default:
		return ""
	}
}
