package filesystem

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"
)

const DefaultFileServerPort = 2652

// 预编译正则表达式（避免每次调用重复编译）
var (
	// CSS 相关
	cssImportRegex = regexp.MustCompile(`@import\s+(?:url\s*\(\s*)?["']([^"']+)["']\s*\)?\s*;`)
	cssUrlRegex    = regexp.MustCompile(`url\(\s*["']?([^"')]+)["']?\s*\)`)

	// HTML 标签
	htmlLinkTagRegex   = regexp.MustCompile(`<link\s+([^>]*)>`)
	htmlScriptTagRegex = regexp.MustCompile(`<script\s+([^>]*)>`)
	htmlImgTagRegex    = regexp.MustCompile(`<img\s+([^>]*)>`)
	htmlVideoTagRegex  = regexp.MustCompile(`<video\s+([^>]*)>`)
	htmlSourceTagRegex = regexp.MustCompile(`<source\s+([^>]*)>`)
	htmlAudioTagRegex  = regexp.MustCompile(`<audio\s+([^>]*)>`)
	htmlIframeTagRegex = regexp.MustCompile(`<iframe\s+([^>]*)>`)
	htmlObjectTagRegex = regexp.MustCompile(`<object\s+([^>]*)>`)
	htmlEmbedTagRegex  = regexp.MustCompile(`<embed\s+([^>]*)>`)

	// HTML 属性
	htmlSrcsetRegex    = regexp.MustCompile(`srcset=["']([^"']+)["']`)
	htmlStyleAttrRegex = regexp.MustCompile(`style=["']([^"']+)["']`)
	htmlStyleTagRegex  = regexp.MustCompile(`<style([^>]*)>([\s\S]*?)</style>`)

	// ES6 模块语句
	es6ImportFromRegex = regexp.MustCompile(`import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']`)
	es6DynamicImport   = regexp.MustCompile(`import\s*\(\s*["']([^"']+)["']\s*\)`)
	es6BareImport      = regexp.MustCompile(`(?m)^\s*import\s+["']([^"']+)["']`)

	// HTML 预览路径修复
	locationPathRegex = regexp.MustCompile(`\blocation\.pathname\b`)
	winDriveRegex    = regexp.MustCompile(`^[A-Za-z]:`)
)

// HTML 属性正则缓存（避免 replaceHtmlTagAttribute 中重复编译）
var attrRegexCache sync.Map // map[string]*regexp.Regexp

// 路径校验 sentinel error（用 errors.Is 匹配，不依赖字符串）
var (
	ErrPathInvalidEncoding = fmt.Errorf("invalid path encoding")
	ErrPathTraversal      = fmt.Errorf("path traversal detected")
	ErrPathUnsafe          = fmt.Errorf("unsafe path")
)

// validateFilePath 校验文件路径安全性（URL解码 + 路径遍历检测 + 安全检查）
// 返回清理后的绝对路径，或 sentinel error
func validateFilePath(rawPath string, logPrefix string) (string, error) {
	decodedPath, err := url.QueryUnescape(rawPath)
	if err != nil {
		return "", ErrPathInvalidEncoding
	}

	if strings.Contains(decodedPath, "..") {
		return "", ErrPathTraversal
	}

	// 去除代理引入的 /localfs/ 前缀（可能有多层）
	clean := decodedPath
	for strings.HasPrefix(clean, "/localfs/") || strings.HasPrefix(clean, "localfs/") {
		clean = strings.TrimPrefix(clean, "/localfs/")
		clean = strings.TrimPrefix(clean, "localfs/")
	}

	// 平台适配：Windows 用反斜杠，Linux/macOS 保持正斜杠
	filePath := filepath.FromSlash(clean)
	filePath = filepath.Clean(filePath)

	// 确保绝对路径（Linux 以 / 开头，Windows 以盘符开头）
	if !filepath.IsAbs(filePath) && len(filePath) > 0 && filePath[0] != '/' && (len(filePath) < 2 || filePath[1] != ':') {
		filePath = "/" + filePath
	}

	if !isSafePath(filePath) {
		return "", ErrPathUnsafe
	}

	return filePath, nil
}

// LocalFileServer 本地文件服务器（独立的 HTTP 服务器）
type LocalFileServer struct {
	server *http.Server
	addr   string
	mu     sync.RWMutex
}

var (
	localFileServer     *LocalFileServer
	localFileServerOnce sync.Once
)

// StartLocalFileServer 启动本地文件服务器（端口被占用时自动递增）
func StartLocalFileServer() (string, error) {
	var initErr error
	localFileServerOnce.Do(func() {
		mux := http.NewServeMux()
		mux.HandleFunc("/localfs/", handleLocalFileRequest)
		mux.HandleFunc("/localfs/html-preview", handleHtmlPreviewRequest)

		addr, srv, err := listenWithFallback(DefaultFileServerPort, mux)
		if err != nil {
			initErr = fmt.Errorf("无法绑定端口(%d起始): %w", DefaultFileServerPort, err)
			return
		}

		localFileServer = &LocalFileServer{server: srv, addr: addr}
		log.Printf("[LocalFileServer] 已启动，监听: %s", addr)
	})

	if localFileServer == nil {
		return "", initErr
	}
	return localFileServer.addr, initErr
}

// listenWithFallback 从 basePort 开始尝试绑定，递增直到成功（最多试 10 次）
// 返回的 server 已在 goroutine 中 Serve(l)，调用方无需再启动
func listenWithFallback(basePort int, handler http.Handler) (addr string, srv *http.Server, err error) {
	for offset := 0; offset < 10; offset++ {
		port := basePort + offset
		addr = fmt.Sprintf("localhost:%d", port)
		l, e := net.Listen("tcp", addr)
		if e == nil {
			srv = &http.Server{Handler: handler}
			go func() {
				if se := srv.Serve(l); se != http.ErrServerClosed {
					log.Printf("[LocalFileServer] 异常退出: %v", se)
				}
			}()
			return addr, srv, nil
		}
		log.Printf("[LocalFileServer] 端口 %d 被占用，尝试 %d...", port, port+1)
	}
	return "", nil, fmt.Errorf("端口 %d-%d 均不可用", basePort, basePort+9)
}

// GetLocalFileServerAddr 返回实际绑定的地址（含动态分配的端口）
func GetLocalFileServerAddr() string {
	if localFileServer == nil { return fmt.Sprintf("http://localhost:%d", DefaultFileServerPort) }
	return localFileServer.addr
}

// handleLocalFileRequest 处理本地文件请求
func handleLocalFileRequest(w http.ResponseWriter, r *http.Request) {
	// CORS 头：允许所有源访问（因为这是本地文件服务器）
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "*")

	// 处理 OPTIONS 预检请求
	if r.Method == http.MethodOptions {
		w.WriteHeader(http.StatusOK)
		return
	}

	// 只处理 GET 请求
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	log.Printf("[LocalFileHandler] 收到请求: %s", r.URL.Path)

	// 从 URL 路径获取文件路径（移除所有 /localfs/ 前缀，兼容代理双重前缀）
	pathPart := r.URL.Path
	for strings.HasPrefix(pathPart, "/localfs/") {
		pathPart = strings.TrimPrefix(pathPart, "/localfs/")
	}
	if pathPart == "" || pathPart == r.URL.Path {
		log.Printf("[LocalFileHandler] 路径前缀无效: original=%s", r.URL.Path)
		http.Error(w, "Invalid path", http.StatusBadRequest)
		return
	}
	// 仅对非绝对路径添加前导 /（Windows 盘符路径如 D:/ 已经是绝对路径，不能加 /）
	if !strings.HasPrefix(pathPart, "/") && !winDriveRegex.MatchString(pathPart) {
		pathPart = "/" + pathPart
	}

	if pathPart == "" || pathPart == r.URL.Path {
		log.Printf("[LocalFileHandler] 路径前缀无效")
		http.Error(w, "Invalid path. Use: /localfs/C:/path/to/file", http.StatusBadRequest)
		return
	}

	// 校验路径安全性（URL解码 + 路径遍历检测 + 安全检查）
	filePath, err := validateFilePath(pathPart, "[LocalFileHandler]")
	if err != nil {
		log.Printf("[LocalFileHandler] 路径校验失败: %v (%s)", err, pathPart)
		switch {
		case errors.Is(err, ErrPathInvalidEncoding):
			http.Error(w, "Invalid path encoding", http.StatusBadRequest)
		case errors.Is(err, ErrPathTraversal):
			http.Error(w, "Path traversal detected", http.StatusForbidden)
		case errors.Is(err, ErrPathUnsafe):
			http.Error(w, "Unsafe path", http.StatusForbidden)
		default:
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		return
	}
	log.Printf("[LocalFileHandler] 最终路径: %s", filePath)

	// 🔒 文件类型白名单检查
	ext := strings.ToLower(filepath.Ext(filePath))
	if !isAllowedFileType(ext) {
		log.Printf("[LocalFileHandler] 不允许的文件类型: %s", ext)
		http.Error(w, fmt.Sprintf("Forbidden file type: %s", ext), http.StatusForbidden)
		return
	}

	// 检查文件是否存在
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Printf("[LocalFileHandler] 文件不存在: %s", filePath)
			http.Error(w, fmt.Sprintf("File not found: %s", filePath), http.StatusNotFound)
		} else {
			log.Printf("[LocalFileHandler] 无法访问文件: %v", err)
			http.Error(w, fmt.Sprintf("Failed to stat file: %v", err), http.StatusInternalServerError)
		}
		return
	}

	// 🔒 限制文件大小（最大500MB）
	const maxFileSize = 500 * 1024 * 1024
	if fileInfo.Size() > maxFileSize {
		log.Printf("[LocalFileHandler] 文件过大: %d bytes", fileInfo.Size())
		http.Error(w, "File too large", http.StatusForbidden)
		return
	}

	// CSS 文件特殊处理：转换内容中的相对路径
	if ext == ".css" {
		content, err := os.ReadFile(filePath)
		if err != nil {
			log.Printf("[LocalFileHandler] 读取CSS文件失败: %v", err)
			http.Error(w, fmt.Sprintf("Failed to read CSS file: %v", err), http.StatusInternalServerError)
			return
		}

		// 获取 CSS 文件所在目录作为 basePath
		basePath := filepath.Dir(filePath)

		// 转换内容中的相对路径
		transformedContent := transformCssContent(string(content), basePath)

		w.Header().Set("Content-Type", "text/css")
		w.Header().Set("Cache-Control", "public, max-age=3600")
		w.Write([]byte(transformedContent))
		log.Printf("[LocalFileHandler] CSS文件转换完成: %s (原始=%d, 转换后=%d)", filePath, len(content), len(transformedContent))
		return
	}

	// JavaScript 文件特殊处理：转换动态 import 路径
	if ext == ".js" || ext == ".mjs" {
		content, err := os.ReadFile(filePath)
		if err != nil {
			log.Printf("[LocalFileHandler] 读取JS文件失败: %v", err)
			http.Error(w, fmt.Sprintf("Failed to read JS file: %v", err), http.StatusInternalServerError)
			return
		}

		// 获取 JS 文件所在目录作为 basePath
		basePath := filepath.Dir(filePath)

		// 转换动态 import 路径
		transformedContent := transformJsDynamicImports(string(content), basePath)

		w.Header().Set("Content-Type", "application/javascript")
		w.Header().Set("Cache-Control", "public, max-age=3600")
		w.Write([]byte(transformedContent))
		log.Printf("[LocalFileHandler] JS文件转换完成: %s (原始=%d, 转换后=%d)", filePath, len(content), len(transformedContent))
		return
	}

	// 打开文件
	file, err := os.Open(filePath)
	if err != nil {
		log.Printf("[LocalFileHandler] 打开文件失败: %v", err)
		http.Error(w, fmt.Sprintf("Failed to open file: %v", err), http.StatusInternalServerError)
		return
	}
	defer file.Close()

	// 设置响应头
	contentType := getContentType(ext)
	w.Header().Set("Content-Type", contentType)
	w.Header().Set("Cache-Control", "public, max-age=3600")
	// 支持 Range 请求
	w.Header().Set("Accept-Ranges", "bytes")

	// 获取文件信息（用于 Range 请求）
	fileStat, err := file.Stat()
	if err != nil {
		log.Printf("[LocalFileHandler] 获取文件信息失败: %v", err)
		http.Error(w, fmt.Sprintf("Failed to stat file: %v", err), http.StatusInternalServerError)
		return
	}

	// 使用 http.ServeContent 实现流式传输（支持 Range 请求）
	http.ServeContent(w, r, filepath.Base(filePath), fileStat.ModTime(), file)
	log.Printf("[LocalFileHandler] 文件传输成功: %s (%d bytes)", filePath, fileStat.Size())
}

// LocalFileHandler 本地文件处理器（兼容旧代码）
// 用于直接从文件系统提供文件，避免 base64 编码
type LocalFileHandler struct {
	http.Handler
}

// NewLocalFileHandler 创建本地文件处理器
func NewLocalFileHandler() *LocalFileHandler {
	// 启动本地文件服务器
	go func() {
		if _, err := StartLocalFileServer(); err != nil {
			log.Printf("[LocalFileHandler] 启动本地文件服务器失败: %v", err)
		}
	}()
	return &LocalFileHandler{}
}

// ServeHTTP 处理 HTTP 请求（代理到 handleLocalFileRequest）
func (h *LocalFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("[LocalFileHandler.ServeHTTP] 收到请求: %s (RawPath: %s)", r.URL.Path, r.URL.RawPath)

	// 检查是否是 /localfs/ 请求
	if !strings.HasPrefix(r.URL.Path, "/localfs/") {
		log.Printf("[LocalFileHandler.ServeHTTP] 路径不匹配 /localfs/ 前缀，返回404")
		// 不是 /localfs/ 请求，返回 404
		http.NotFound(w, r)
		return
	}

	// 直接调用实际的请求处理器
	handleLocalFileRequest(w, r)
}

// getContentType 根据文件扩展名返回 MIME 类型
// 使用统一的文件类型管理器
func getContentType(ext string) string {
	return defaultFileTypeManager.GetMIMEType(ext)
}

// isAllowedFileType 检查文件类型是否在白名单中
func isAllowedFileType(ext string) bool {
	return defaultFileTypeManager.IsAllowed(ext)
}

// Shutdown 优雅关闭文件服务器
func (lfs *LocalFileServer) Shutdown() error {
	if lfs == nil || lfs.server == nil {
		return nil
	}

	lfs.mu.Lock()
	defer lfs.mu.Unlock()

	// 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	log.Printf("[LocalFileServer] 正在关闭...")

	if err := lfs.server.Shutdown(ctx); err != nil {
		log.Printf("[LocalFileServer] 关闭失败: %v", err)
		return err
	}

	log.Printf("[LocalFileServer] 已关闭")
	return nil
}

// ShutdownLocalFileServer 关闭全局文件服务器
func ShutdownLocalFileServer() error {
	if localFileServer != nil {
		return localFileServer.Shutdown()
	}
	return nil
}

// transformCssContent 转换 CSS 内容中的相对路径
// basePath: CSS 文件所在目录的绝对路径
func transformCssContent(content string, basePath string) string {
	// 1. 处理 @import 语句
	content = cssImportRegex.ReplaceAllStringFunc(content, func(match string) string {
		submatches := cssImportRegex.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}
		relativePath := submatches[1]

		// 跳过绝对 URL 和数据 URI
		if isAbsoluteURL(relativePath) || strings.HasPrefix(relativePath, "data:") {
			return match
		}

		absolutePath := resolveCssRelativePath(basePath, relativePath)
		if absolutePath == "" {
			return match
		}

		return fmt.Sprintf(`@import url("%s");`, toLocalServerUrl(absolutePath))
	})

	// 2. 处理 url() 语句
	content = cssUrlRegex.ReplaceAllStringFunc(content, func(match string) string {
		submatches := cssUrlRegex.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}
		relativePath := strings.TrimSpace(submatches[1])

		// 跳过绝对 URL、数据 URI 和绝对路径
		if isAbsoluteURL(relativePath) || strings.HasPrefix(relativePath, "data:") || strings.HasPrefix(relativePath, "/") || relativePath == "" {
			return match
		}

		absolutePath := resolveCssRelativePath(basePath, relativePath)
		if absolutePath == "" {
			return match
		}

		return fmt.Sprintf(`url("%s")`, toLocalServerUrl(absolutePath))
	})

	return content
}

// resolveCssRelativePath 解析 CSS 中的相对路径为绝对路径
func resolveCssRelativePath(basePath, relativePath string) string {
	// 清理路径
	relativePath = strings.TrimSpace(relativePath)
	if relativePath == "" {
		return ""
	}

	// 移除 ./ 前缀
	relativePath = strings.TrimPrefix(relativePath, "./")

	// 使用 filepath.Join 处理 ../ 等
	// 注意：需要先将 / 转换为 \ 以便 Windows 路径正确处理
	relativePath = strings.ReplaceAll(relativePath, "/", string(filepath.Separator))
	absolutePath := filepath.Join(basePath, relativePath)

	// 清理路径并转换回 /
	absolutePath = filepath.Clean(absolutePath)
	absolutePath = strings.ReplaceAll(absolutePath, "\\", "/")

	return absolutePath
}

// toLocalServerUrl 将绝对路径转换为 /localfs/ URL（带 URL 编码）
func toLocalServerUrl(absolutePath string) string {
	// 确保路径使用 /
	absolutePath = strings.ReplaceAll(absolutePath, "\\", "/")
	// 对路径进行 URL 编码（分段编码，保留 /）
	parts := strings.Split(absolutePath, "/")
	for i, part := range parts {
		// Windows 驱动器字母（如 E:）需要特殊处理，冒号必须编码
		if len(part) == 2 && part[1] == ':' {
			// 将 "E:" 转换为 "E%3A"
			parts[i] = string(part[0]) + "%3A"
		} else {
			parts[i] = url.PathEscape(part)
		}
	}
	return "/localfs/" + strings.Join(parts, "/")
}

// isAbsoluteURL 检查是否为绝对 URL（http://, https://, //）
func isAbsoluteURL(path string) bool {
	path = strings.ToLower(path)
	return strings.HasPrefix(path, "http://") ||
		strings.HasPrefix(path, "https://") ||
		strings.HasPrefix(path, "//")
}

// handleHtmlPreviewRequest 处理 HTML 预览请求
// 参数：
//   - path: HTML 文件绝对路径（URL 编码）
//   - theme: 主题（light / dark）
func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) {
	// CORS
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "*")

	// 处理 OPTIONS 预检请求
	if r.Method == http.MethodOptions {
		w.WriteHeader(http.StatusOK)
		return
	}

	// 只处理 GET 请求
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// 解析参数
	rawPath := r.URL.Query().Get("path")
	theme := r.URL.Query().Get("theme")
	if theme == "" {
		theme = "light"
	}

	// 校验路径安全性（URL解码 + 路径遍历检测 + 安全检查）
	filePath, err := validateFilePath(rawPath, "[HtmlPreview]")
	if err != nil {
		log.Printf("[HtmlPreview] 路径校验失败: %v (%s)", err, rawPath)
		switch {
		case errors.Is(err, ErrPathInvalidEncoding):
			http.Error(w, "Invalid path encoding", http.StatusBadRequest)
		case errors.Is(err, ErrPathTraversal):
			http.Error(w, "Path traversal detected", http.StatusForbidden)
		case errors.Is(err, ErrPathUnsafe):
			http.Error(w, "Unsafe path", http.StatusForbidden)
		default:
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		return
	}

	log.Printf("[HtmlPreview] 收到请求: path=%s, theme=%s", filePath, theme)

	// 读取文件
	content, err := os.ReadFile(filePath)
	if err != nil {
		log.Printf("[HtmlPreview] 读取文件失败: %v", err)
		http.Error(w, "File not found", http.StatusNotFound)
		return
	}

	// 获取文件所在目录（用于解析相对路径）
	baseDir := filepath.Dir(filePath)

	// 转换资源路径（将相对路径和绝对路径都转换为完整的本地文件服务器 URL）
	processedContent := transformHtmlResourcePaths(string(content), baseDir)

	// 修复 JS 中基于 location.pathname 的相对路径计算
	// 预览模式下 location.pathname = "/localfs/html-preview"，与实际文件路径不一致
	// ⚠️ 会替换所有出现位置（含JS字符串内），HTML预览场景下可接受
	correctPathname := `"/localfs/` + strings.ReplaceAll(baseDir, "\\", "/") + `/`
	processedContent = locationPathRegex.ReplaceAllString(processedContent, correctPathname)

	// 注入链接点击拦截脚本
	finalContent := injectLinkInterceptor(processedContent)

	// 返回处理后的 HTML
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache")
	w.Write([]byte(finalContent))

	log.Printf("[HtmlPreview] 处理完成: %s (%d -> %d bytes)", filePath, len(content), len(finalContent))
}

// transformHtmlResourcePaths 转换 HTML 中的资源路径为本地文件服务器 URL
func transformHtmlResourcePaths(htmlContent string, baseDir string) string {
	if baseDir == "" {
		return htmlContent
	}

	result := htmlContent

	// 1. 处理 HTML 标签资源
	tagConfigs := []struct {
		pattern *regexp.Regexp
		attr    string
	}{
		{htmlLinkTagRegex, "href"},
		{htmlScriptTagRegex, "src"},
		{htmlImgTagRegex, "src"},
		{htmlVideoTagRegex, "src"},
		{htmlVideoTagRegex, "poster"},
		{htmlSourceTagRegex, "src"},
		{htmlAudioTagRegex, "src"},
		{htmlIframeTagRegex, "src"},
		{htmlObjectTagRegex, "data"},
		{htmlEmbedTagRegex, "src"},
	}

	for _, config := range tagConfigs {
		result = replaceHtmlTagAttribute(result, config.pattern, config.attr, baseDir)
	}

	// 2. 处理 srcset 属性
	result = replaceHtmlSrcset(result, baseDir)

	// 3. 处理内联 style 属性中的 url()
	result = replaceHtmlInlineStyleUrls(result, baseDir)

	// 4. 处理 <style> 标签内的内容
	result = replaceHtmlStyleTagContent(result, baseDir)

	// 5. 处理 ES6 模块语句（使用预编译正则）
	result = es6ImportFromRegex.ReplaceAllStringFunc(result, func(match string) string {
		submatches := es6ImportFromRegex.FindStringSubmatch(match)
		if len(submatches) < 3 {
			return match
		}
		modulePath := submatches[2]
		if isAbsoluteURL(modulePath) || strings.HasPrefix(modulePath, "data:") {
			return match
		}
		return fmt.Sprintf(`import %s from "%s"`, submatches[1], resolveHtmlPathToUrl(baseDir, modulePath))
	})

	result = es6DynamicImport.ReplaceAllStringFunc(result, func(match string) string {
		submatches := es6DynamicImport.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}
		modulePath := submatches[1]
		if isAbsoluteURL(modulePath) || strings.HasPrefix(modulePath, "data:") {
			return match
		}
		return fmt.Sprintf(`import("%s")`, resolveHtmlPathToUrl(baseDir, modulePath))
	})

	return result
}

// replaceHtmlTagAttribute 替换 HTML 标签中的属性路径
func replaceHtmlTagAttribute(html string, pattern *regexp.Regexp, attrName string, baseDir string) string {
	return pattern.ReplaceAllStringFunc(html, func(match string) string {
		// 提取属性值（使用缓存的正则）
		var attrRegex *regexp.Regexp
		if v, ok := attrRegexCache.Load(attrName); ok {
			attrRegex = v.(*regexp.Regexp)
		} else {
			attrRegex = regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, attrName))
			attrRegexCache.Store(attrName, attrRegex)
		}
		attrMatch := attrRegex.FindStringSubmatch(match)
		if attrMatch == nil {
			return match
		}

		relativePath := attrMatch[1]
		if isAbsoluteURL(relativePath) || strings.HasPrefix(relativePath, "data:") {
			return match
		}

		newUrl := resolveHtmlPathToUrl(baseDir, relativePath)
		return strings.Replace(match, attrMatch[0], fmt.Sprintf(`%s="%s"`, attrName, newUrl), 1)
	})
}

// replaceHtmlSrcset 处理 srcset 属性
func replaceHtmlSrcset(html string, baseDir string) string {
	return htmlSrcsetRegex.ReplaceAllStringFunc(html, func(match string) string {
		submatches := htmlSrcsetRegex.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}

		srcset := submatches[1]
		var newItems []string
		for _, item := range strings.Split(srcset, ",") {
			parts := strings.Fields(strings.TrimSpace(item))
			if len(parts) == 0 {
				continue
			}
			url := parts[0]
			descriptor := ""
			if len(parts) > 1 {
				descriptor = " " + strings.Join(parts[1:], " ")
			}
			if isAbsoluteURL(url) || strings.HasPrefix(url, "data:") {
				newItems = append(newItems, item)
			} else {
				newItems = append(newItems, resolveHtmlPathToUrl(baseDir, url)+descriptor)
			}
		}
		return fmt.Sprintf(`srcset="%s"`, strings.Join(newItems, ", "))
	})
}

// replaceHtmlInlineStyleUrls 处理内联 style 属性中的 url()
func replaceHtmlInlineStyleUrls(html string, baseDir string) string {
	return htmlStyleAttrRegex.ReplaceAllStringFunc(html, func(match string) string {
		submatches := htmlStyleAttrRegex.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}

		styleContent := submatches[1]
		newStyle := cssUrlRegex.ReplaceAllStringFunc(styleContent, func(urlMatch string) string {
			urlSubmatches := cssUrlRegex.FindStringSubmatch(urlMatch)
			if len(urlSubmatches) < 2 {
				return urlMatch
			}
			url := strings.TrimSpace(urlSubmatches[1])
			if isAbsoluteURL(url) || strings.HasPrefix(url, "data:") {
				return urlMatch
			}
			return fmt.Sprintf(`url("%s")`, resolveHtmlPathToUrl(baseDir, url))
		})
		return fmt.Sprintf(`style="%s"`, newStyle)
	})
}

// replaceHtmlStyleTagContent 处理 <style> 标签内的内容
func replaceHtmlStyleTagContent(html string, baseDir string) string {
	return htmlStyleTagRegex.ReplaceAllStringFunc(html, func(match string) string {
		submatches := htmlStyleTagRegex.FindStringSubmatch(match)
		if len(submatches) < 3 {
			return match
		}

		attrs := submatches[1]
		content := submatches[2]

		// 处理 @import（使用预编译正则）
		content = cssImportRegex.ReplaceAllStringFunc(content, func(m string) string {
			sm := cssImportRegex.FindStringSubmatch(m)
			if len(sm) < 2 {
				return m
			}
			url := sm[1]
			if isAbsoluteURL(url) || strings.HasPrefix(url, "data:") {
				return m
			}
			return fmt.Sprintf(`@import url("%s");`, resolveHtmlPathToUrl(baseDir, url))
		})

		// 处理 url()（使用预编译正则）
		content = cssUrlRegex.ReplaceAllStringFunc(content, func(m string) string {
			sm := cssUrlRegex.FindStringSubmatch(m)
			if len(sm) < 2 {
				return m
			}
			url := strings.TrimSpace(sm[1])
			if isAbsoluteURL(url) || strings.HasPrefix(url, "data:") {
				return m
			}
			return fmt.Sprintf(`url("%s")`, resolveHtmlPathToUrl(baseDir, url))
		})

		return fmt.Sprintf(`<style%s>%s</style>`, attrs, content)
	})
}

// resolveHtmlPathToUrl 解析路径并转换为本地文件服务器 URL
// 支持：相对路径 (./xxx, ../xxx)、绝对路径 (/xxx)
func resolveHtmlPathToUrl(baseDir string, path string) string {
	path = strings.TrimSpace(path)
	if path == "" {
		return ""
	}

	// 统一使用 / 作为分隔符
	baseDir = strings.ReplaceAll(baseDir, "\\", "/")

	// 处理以 / 开头的绝对路径（相对于网站根目录）
	// 对于 dist/index.html，/assets/... 应该被解析为 dist/assets/...
	if strings.HasPrefix(path, "/") {
		path = path[1:] // 移除开头的 /
	}

	// 移除 ./ 前缀
	path = strings.TrimPrefix(path, "./")

	// 使用 filepath.Join 进行路径拼接（它会自动处理 ../）
	pathBackslash := strings.ReplaceAll(path, "/", string(filepath.Separator))
	absolutePath := filepath.Join(baseDir, pathBackslash)

	// 清理路径并转换回 /
	absolutePath = filepath.Clean(absolutePath)
	absolutePath = strings.ReplaceAll(absolutePath, "\\", "/")

	return toLocalServerUrl(absolutePath)
}

// transformJsDynamicImports 转换 JavaScript 文件中的 import 路径
func transformJsDynamicImports(jsContent string, baseDir string) string {
	if baseDir == "" {
		return jsContent
	}

	result := jsContent

	// 处理动态 import: import("...")（使用预编译正则）
	result = es6DynamicImport.ReplaceAllStringFunc(result, func(match string) string {
		submatches := es6DynamicImport.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}
		modulePath := submatches[1]
		if isAbsoluteURL(modulePath) || strings.HasPrefix(modulePath, "data:") || strings.HasPrefix(modulePath, "/localfs/") {
			return match
		}
		return fmt.Sprintf(`import("%s")`, resolveHtmlPathToUrl(baseDir, modulePath))
	})

	// 处理静态 import: import ... from "..."（使用预编译正则）
	result = es6ImportFromRegex.ReplaceAllStringFunc(result, func(match string) string {
		submatches := es6ImportFromRegex.FindStringSubmatch(match)
		if len(submatches) < 3 {
			return match
		}
		importClause := submatches[1]
		modulePath := submatches[2]
		if isAbsoluteURL(modulePath) || strings.HasPrefix(modulePath, "data:") || strings.HasPrefix(modulePath, "/localfs/") {
			return match
		}
		return fmt.Sprintf(`import %s from "%s"`, importClause, resolveHtmlPathToUrl(baseDir, modulePath))
	})

	// 处理裸 import: import "..."（使用预编译正则）
	result = es6BareImport.ReplaceAllStringFunc(result, func(match string) string {
		submatches := es6BareImport.FindStringSubmatch(match)
		if len(submatches) < 2 {
			return match
		}
		modulePath := submatches[1]
		if isAbsoluteURL(modulePath) || strings.HasPrefix(modulePath, "data:") || strings.HasPrefix(modulePath, "/localfs/") {
			return match
		}
		return fmt.Sprintf(`import "%s"`, resolveHtmlPathToUrl(baseDir, modulePath))
	})

	return result
}

// injectLinkInterceptor 注入链接点击拦截脚本
func injectLinkInterceptor(htmlContent string) string {
	script := `
    <script>
      document.addEventListener('click', function(e) {
        var target = e.target;
        while (target && target.tagName !== 'A') {
          target = target.parentElement;
        }
        if (target && target.tagName === 'A') {
          var href = target.getAttribute('href');
          if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('#') && !href.startsWith('javascript:')) {
            e.preventDefault();
            e.stopPropagation();
            window.parent.postMessage({ type: 'openLocalFile', path: href }, '*');
          }
        }
      }, true);
    </script>
`

	// 在 </body> 前插入
	if strings.Contains(htmlContent, "</body>") {
		return strings.Replace(htmlContent, "</body>", script+"</body>", 1)
	}
	// 没有 body 标签，在末尾插入
	return htmlContent + script
}
