package api

import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	yaml "gopkg.in/yaml.v3"

	"github.com/mathieu/project-notes/internal/indexer"
)

// TreeNode représente un nœud dans l'arborescence des fichiers
type TreeNode struct {
	Name     string      `json:"name"`
	Path     string      `json:"path"`
	IsDir    bool        `json:"isDir"`
	Children []*TreeNode `json:"children,omitempty"`
}

// Handler gère toutes les routes de l'API.
type Handler struct {
	notesDir  string
	idx       *indexer.Indexer
	templates *template.Template
	logger    *log.Logger
}

// NewHandler construit un handler unifié pour l'API.
func NewHandler(notesDir string, idx *indexer.Indexer, tpl *template.Template, logger *log.Logger) *Handler {
	return &Handler{
		notesDir:  notesDir,
		idx:       idx,
		templates: tpl,
		logger:    logger,
	}
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	h.logger.Printf("%s %s", r.Method, path)

	// REST API v1 endpoints
	if strings.HasPrefix(path, "/api/v1/notes") {
		h.handleRESTNotes(w, r)
		return
	}

	// Legacy/HTML endpoints
	if strings.HasPrefix(path, "/api/search") {
		h.handleSearch(w, r)
		return
	}
	if path == "/api/folders/create" {
		h.handleCreateFolder(w, r)
		return
	}
	if path == "/api/files/move" {
		h.handleMoveFile(w, r)
		return
	}
	if path == "/api/notes/new-auto" {
		h.handleNewNoteAuto(w, r)
		return
	}
	if path == "/api/notes/new-prompt" {
		h.handleNewNotePrompt(w, r)
		return
	}
	if path == "/api/notes/create-custom" {
		h.handleCreateCustomNote(w, r)
		return
	}
	if path == "/api/home" {
		h.handleHome(w, r)
		return
	}
	if strings.HasPrefix(path, "/api/notes/") {
		h.handleNotes(w, r)
		return
	}
	if path == "/api/tree" {
		h.handleFileTree(w, r)
		return
	}
	http.NotFound(w, r)
}

// buildFileTree construit l'arborescence hiérarchique des fichiers et dossiers
func (h *Handler) buildFileTree() (*TreeNode, error) {
	root := &TreeNode{
		Name:     "notes",
		Path:     "",
		IsDir:    true,
		Children: make([]*TreeNode, 0),
	}

	err := filepath.WalkDir(h.notesDir, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Ignorer le répertoire racine lui-même
		if path == h.notesDir {
			return nil
		}

		relPath, err := filepath.Rel(h.notesDir, path)
		if err != nil {
			return err
		}

		// Ignorer les fichiers cachés
		if strings.HasPrefix(d.Name(), ".") {
			if d.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		// Pour les fichiers, ne garder que les .md
		if !d.IsDir() && !strings.EqualFold(filepath.Ext(path), ".md") {
			return nil
		}

		// Créer le nœud
		node := &TreeNode{
			Name:     d.Name(),
			Path:     relPath,
			IsDir:    d.IsDir(),
			Children: make([]*TreeNode, 0),
		}

		// Trouver le parent et ajouter ce nœud
		h.insertNode(root, node, relPath)

		return nil
	})

	if err != nil {
		return nil, err
	}

	// Trier les enfants (dossiers d'abord, puis fichiers, alphabétiquement)
	h.sortTreeNode(root)

	return root, nil
}

// insertNode insère un nœud dans l'arbre à la bonne position
func (h *Handler) insertNode(root *TreeNode, node *TreeNode, path string) {
	parts := strings.Split(filepath.ToSlash(path), "/")

	current := root
	for i := 0; i < len(parts)-1; i++ {
		found := false
		for _, child := range current.Children {
			if child.Name == parts[i] && child.IsDir {
				current = child
				found = true
				break
			}
		}
		if !found {
			// Créer le dossier intermédiaire
			newDir := &TreeNode{
				Name:     parts[i],
				Path:     strings.Join(parts[:i+1], "/"),
				IsDir:    true,
				Children: make([]*TreeNode, 0),
			}
			current.Children = append(current.Children, newDir)
			current = newDir
		}
	}

	current.Children = append(current.Children, node)
}

// sortTreeNode trie récursivement les enfants d'un nœud
func (h *Handler) sortTreeNode(node *TreeNode) {
	if !node.IsDir || len(node.Children) == 0 {
		return
	}

	sort.Slice(node.Children, func(i, j int) bool {
		// Les dossiers avant les fichiers
		if node.Children[i].IsDir != node.Children[j].IsDir {
			return node.Children[i].IsDir
		}
		// Puis alphabétiquement
		return strings.ToLower(node.Children[i].Name) < strings.ToLower(node.Children[j].Name)
	})

	// Trier récursivement
	for _, child := range node.Children {
		h.sortTreeNode(child)
	}
}

func (h *Handler) handleFileTree(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}

	tree, err := h.buildFileTree()
	if err != nil {
		h.logger.Printf("erreur lors de la construction de l arborescence: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
		return
	}

	data := struct {
		Tree *TreeNode
	}{
		Tree: tree,
	}

	err = h.templates.ExecuteTemplate(w, "file-tree.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template de l arborescence: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}

	// Générer le contenu Markdown avec la liste de toutes les notes
	content := h.generateHomeMarkdown()

	// Utiliser le template editor.html pour afficher la page d'accueil
	data := struct {
		Filename string
		Content  string
		IsHome   bool
	}{
		Filename: "🏠 Accueil - Index des notes",
		Content:  content,
		IsHome:   true,
	}

	err := h.templates.ExecuteTemplate(w, "editor.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template home: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

// generateHomeMarkdown génère le contenu Markdown de la page d'accueil
func (h *Handler) generateHomeMarkdown() string {
	var sb strings.Builder

	// En-tête
	sb.WriteString("# 📚 Index des Notes\n\n")
	sb.WriteString("_Mise à jour automatique • " + time.Now().Format("02/01/2006 à 15:04") + "_\n\n")
	sb.WriteString("---\n\n")

	// Construire l'arborescence
	tree, err := h.buildFileTree()
	if err != nil {
		h.logger.Printf("erreur lors de la construction de l'arbre: %v", err)
		sb.WriteString("❌ Erreur lors de la génération de l'index\n")
		return sb.String()
	}

	// Compter le nombre de notes
	noteCount := h.countNotes(tree)
	sb.WriteString(fmt.Sprintf("**%d note(s) au total**\n\n", noteCount))

	// Générer l'arborescence en Markdown
	h.generateMarkdownTree(&sb, tree, 0)

	return sb.String()
}

// countNotes compte le nombre de fichiers .md dans l'arborescence
func (h *Handler) countNotes(node *TreeNode) int {
	count := 0
	if !node.IsDir {
		count = 1
	}
	for _, child := range node.Children {
		count += h.countNotes(child)
	}
	return count
}

// generateMarkdownTree génère l'arborescence en HTML avec accordéon
func (h *Handler) generateMarkdownTree(sb *strings.Builder, node *TreeNode, depth int) {
	// Ignorer le nœud racine
	if depth == 0 {
		sb.WriteString("<div class=\"note-tree\">\n")
		for _, child := range node.Children {
			h.generateMarkdownTree(sb, child, depth+1)
		}
		sb.WriteString("</div>\n")
		return
	}

	indent := strings.Repeat("  ", depth-1)
	// Créer un ID unique basé sur le path
	safeID := strings.ReplaceAll(strings.ReplaceAll(node.Path, "/", "-"), "\\", "-")

	if node.IsDir {
		// Dossier - affichage avec accordéon
		// Utiliser des classes CSS au lieu de styles inline
		indentClass := fmt.Sprintf("indent-level-%d", depth)
		sb.WriteString(fmt.Sprintf("%s<div class=\"folder %s\">\n", indent, indentClass))
		sb.WriteString(fmt.Sprintf("%s  <div class=\"folder-header\" onclick=\"toggleFolder('%s')\">\n", indent, safeID))
		sb.WriteString(fmt.Sprintf("%s    <span class=\"folder-icon\" id=\"icon-%s\">📁</span>\n", indent, safeID))
		sb.WriteString(fmt.Sprintf("%s    <strong>%s</strong>\n", indent, node.Name))
		sb.WriteString(fmt.Sprintf("%s  </div>\n", indent))
		sb.WriteString(fmt.Sprintf("%s  <div class=\"folder-content\" id=\"folder-%s\">\n", indent, safeID))

		for _, child := range node.Children {
			h.generateMarkdownTree(sb, child, depth+1)
		}

		sb.WriteString(fmt.Sprintf("%s  </div>\n", indent))
		sb.WriteString(fmt.Sprintf("%s</div>\n", indent))
	} else {
		// Fichier - créer un lien HTML cliquable avec HTMX
		displayName := strings.TrimSuffix(node.Name, ".md")
		// Échapper le path pour l'URL
		escapedPath := strings.ReplaceAll(node.Path, "\\", "/")
		indentClass := fmt.Sprintf("indent-level-%d", depth)
		sb.WriteString(fmt.Sprintf("%s<div class=\"file %s\">\n", indent, indentClass))
		sb.WriteString(fmt.Sprintf("%s  <span class=\"file-icon\">📄</span>\n", indent))
		sb.WriteString(fmt.Sprintf("%s  <a href=\"#\" hx-get=\"/api/notes/%s\" hx-target=\"#editor-container\" hx-swap=\"innerHTML\">%s</a>\n",
			indent, escapedPath, displayName))
		sb.WriteString(fmt.Sprintf("%s</div>\n", indent))
	}
}

func (h *Handler) handleNewNoteAuto(w http.ResponseWriter, r *http.Request) {
	// Generate a unique filename
	baseFilename := "nouvelle-note"
	filename := ""
	for i := 1; ; i++ {
		tempFilename := fmt.Sprintf("%s-%d.md", baseFilename, i)
		fullPath := filepath.Join(h.notesDir, tempFilename)
		if _, err := os.Stat(fullPath); os.IsNotExist(err) {
			filename = tempFilename
			break
		}
	}

	h.createAndRenderNote(w, r, filename)
}

func (h *Handler) handleNewNotePrompt(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}
	err := h.templates.ExecuteTemplate(w, "new-note-prompt.html", nil)
	if err != nil {
		h.logger.Printf("erreur d execution du template new-note-prompt: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

func (h *Handler) handleCreateCustomNote(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Error(w, "lecture du formulaire impossible", http.StatusBadRequest)
		return
	}
	filename := r.FormValue("filename")
	if filename == "" {
		http.Error(w, "nom de fichier manquant", http.StatusBadRequest)
		return
	}

	// Securite : nettoyer le nom du fichier
	filename = filepath.Clean(filename)
	if strings.HasPrefix(filename, "..") || !strings.HasSuffix(filename, ".md") {
		http.Error(w, "nom de fichier invalide", http.StatusBadRequest)
		return
	}

	fullPath := filepath.Join(h.notesDir, filename)
	if _, err := os.Stat(fullPath); err == nil {
		http.Error(w, "une note avec ce nom existe deja", http.StatusConflict)
		return
	}

	h.createAndRenderNote(w, r, filename)
}

// createAndRenderNote est une fonction utilitaire pour creer et rendre une nouvelle note
func (h *Handler) createAndRenderNote(w http.ResponseWriter, r *http.Request, filename string) {
	// Prepare initial front matter for a new note
	now := time.Now()
	newFM := indexer.FullFrontMatter{
		Title:        strings.Title(strings.ReplaceAll(strings.TrimSuffix(filename, filepath.Ext(filename)), "-", " ")),
		Date:         now.Format("02-01-2006"),
		LastModified: now.Format("02-01-2006:15:04"),
		Tags:         []string{"default"}, // Default tag for new notes
	}

	fmBytes, err := yaml.Marshal(newFM)
	if err != nil {
		h.logger.Printf("erreur de marshalling du front matter pour nouvelle note: %v", err)
		http.Error(w, "erreur interne lors de la generation du front matter", http.StatusInternalServerError)
		return
	}

	// Combine new front matter with a placeholder body
	initialContent := "---\n" + string(fmBytes) + "---\n\n# " + newFM.Title + "\n\nCommencez à écrire votre note ici..."

	data := struct {
		Filename string
		Content  string
		IsHome   bool
	}{
		Filename: filename,
		Content:  initialContent,
		IsHome:   false,
	}

	err = h.templates.ExecuteTemplate(w, "editor.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template editeur pour nouvelle note: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

func (h *Handler) handleSearch(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}

	query := strings.TrimSpace(r.URL.Query().Get("query"))
	if query == "" {
		query = strings.TrimSpace(r.URL.Query().Get("tag"))
	}

	results := h.idx.SearchDocuments(query)

	data := struct {
		Query   string
		Results []indexer.SearchResult
	}{
		Query:   query,
		Results: results,
	}

	err := h.templates.ExecuteTemplate(w, "search-results.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template de recherche: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

func (h *Handler) handleNotes(w http.ResponseWriter, r *http.Request) {
	filename, err := h.extractFilename(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	switch r.Method {
	case http.MethodGet:
		h.handleGetNote(w, r, filename)
	case http.MethodPost:
		h.handlePostNote(w, r, filename)
	case http.MethodDelete:
		h.handleDeleteNote(w, r, filename)
	default:
		w.Header().Set("Allow", "GET, POST, DELETE")
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
	}
}

func (h *Handler) handleDeleteNote(w http.ResponseWriter, r *http.Request, filename string) {
	fullPath := filepath.Join(h.notesDir, filename)

	if err := os.Remove(fullPath); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			http.Error(w, "note introuvable", http.StatusNotFound)
			return
		}
		h.logger.Printf("erreur de suppression du fichier %s: %v", filename, err)
		http.Error(w, "suppression impossible", http.StatusInternalServerError)
		return
	}

	// Re-indexation en arriere-plan
	go func() {
		if err := h.idx.Load(h.notesDir); err != nil {
			h.logger.Printf("echec de la reindexation post-suppression: %v", err)
		}
	}()

	// Repondre a htmx pour vider l'editeur et rafraichir l'arborescence
	h.renderFileTreeOOB(w)
	io.WriteString(w, `<p>Note "`+filename+`" supprimée.</p>`)
}

func (h *Handler) handleGetNote(w http.ResponseWriter, r *http.Request, filename string) {
	fullPath := filepath.Join(h.notesDir, filename)
	content, err := os.ReadFile(fullPath)
	if err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			h.logger.Printf("erreur de lecture du fichier %s: %v", filename, err)
			http.Error(w, "lecture impossible", http.StatusInternalServerError)
			return
		}
		// Le fichier n'existe pas, créer un front matter initial
		now := time.Now()
		newFM := indexer.FullFrontMatter{
			Title:        strings.Title(strings.ReplaceAll(strings.TrimSuffix(filename, filepath.Ext(filename)), "-", " ")),
			Date:         now.Format("02-01-2006"),
			LastModified: now.Format("02-01-2006:15:04"),
			Tags:         []string{},
		}

		fmBytes, err := yaml.Marshal(newFM)
		if err != nil {
			h.logger.Printf("erreur de marshalling du front matter pour nouvelle note: %v", err)
			http.Error(w, "erreur interne lors de la generation du front matter", http.StatusInternalServerError)
			return
		}

		// Générer le contenu initial avec front matter
		initialContent := "---\n" + string(fmBytes) + "---\n\n# " + newFM.Title + "\n\nCommencez à écrire votre note ici..."
		content = []byte(initialContent)
	}

	data := struct {
		Filename string
		Content  string
		IsHome   bool
	}{
		Filename: filename,
		Content:  string(content),
		IsHome:   false,
	}

	err = h.templates.ExecuteTemplate(w, "editor.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template editeur: %v", err)
		http.Error(w, "erreur interne", http.StatusInternalServerError)
	}
}

func (h *Handler) handlePostNote(w http.ResponseWriter, r *http.Request, filename string) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "lecture du formulaire impossible", http.StatusBadRequest)
		return
	}
	incomingContent := r.FormValue("content")

	fullPath := filepath.Join(h.notesDir, filename)
	isNewFile := false
	if _, err := os.Stat(fullPath); errors.Is(err, os.ErrNotExist) {
		isNewFile = true
	}

	// Extract existing front matter and body from incoming content
	var currentFM indexer.FullFrontMatter
	var bodyContent string
	var err error

	// Use a strings.Reader to pass the content to the extractor
	currentFM, bodyContent, err = indexer.ExtractFrontMatterAndBodyFromReader(strings.NewReader(incomingContent))
	if err != nil {
		h.logger.Printf("erreur d'extraction du front matter: %v", err)
		http.Error(w, "erreur interne lors de l'analyse du contenu", http.StatusInternalServerError)
		return
	}

	// Prepare new front matter
	newFM := currentFM

	// Set Title
	if newFM.Title == "" {
		newFM.Title = strings.TrimSuffix(filename, filepath.Ext(filename))
		newFM.Title = strings.ReplaceAll(newFM.Title, "-", " ")
		newFM.Title = strings.Title(newFM.Title)
	}

	// Set Date (creation date)
	now := time.Now()
	if isNewFile || newFM.Date == "" { // Check for empty string
		newFM.Date = now.Format("02-01-2006")
	}

	// Set LastModified
	newFM.LastModified = now.Format("02-01-2006:15:04")

	// Marshal new front matter to YAML
	fmBytes, err := yaml.Marshal(newFM)
	if err != nil {
		h.logger.Printf("erreur de marshalling du front matter: %v", err)
		http.Error(w, "erreur interne lors de la generation du front matter", http.StatusInternalServerError)
		return
	}

	// Combine new front matter with body
	finalContent := "---\n" + string(fmBytes) + "---\n" + bodyContent

	if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
		h.logger.Printf("erreur de creation du repertoire pour %s: %v", filename, err)
		http.Error(w, "creation repertoire impossible", http.StatusInternalServerError)
		return
	}

	if err := os.WriteFile(fullPath, []byte(finalContent), 0o644); err != nil {
		h.logger.Printf("erreur d ecriture du fichier %s: %v", filename, err)
		http.Error(w, "ecriture impossible", http.StatusInternalServerError)
		return
	}

	// Re-indexation en arriere-plan pour ne pas ralentir la reponse
	go func() {
		if err := h.idx.Load(h.notesDir); err != nil {
			h.logger.Printf("echec de la reindexation post-ecriture: %v", err)
		}
	}()

	// Repondre a htmx pour vider l'editeur et rafraichir l'arborescence
	h.renderFileTreeOOB(w)

    // Répondre avec les statuts de sauvegarde OOB
    nowStr := time.Now().Format("15:04:05")
    oobStatus := fmt.Sprintf(`
        <span id="auto-save-status" hx-swap-oob="true">Enregistré à %s</span>
        <span id="save-status" hx-swap-oob="true"></span>`, nowStr)
    io.WriteString(w, oobStatus)
}

func (h *Handler) extractFilename(path string) (string, error) {
	const prefix = "/api/notes/"
	if !strings.HasPrefix(path, prefix) {
		return "", errors.New("chemin invalide")
	}

	rel := strings.TrimPrefix(path, prefix)
	rel = strings.TrimSpace(rel)
	if rel == "" {
		return "", errors.New("fichier manquant")
	}

	rel = filepath.Clean(rel)
	if rel == "." || strings.HasPrefix(rel, "..") {
		return "", errors.New("nom de fichier invalide")
	}

	if filepath.Ext(rel) != ".md" {
		return "", errors.New("extension invalide")
	}

	return rel, nil
}

// renderFileTreeOOB genere le HTML de l'arborescence des fichiers pour un swap out-of-band.
func (h *Handler) renderFileTreeOOB(w http.ResponseWriter) {
	tree, err := h.buildFileTree()
	if err != nil {
		h.logger.Printf("erreur lors de la construction de l arborescence pour OOB: %v", err)
		return // Don't fail the main request, just log
	}

	data := struct {
		Tree *TreeNode
	}{
		Tree: tree,
	}

	// Render the file tree template into a buffer
	var buf strings.Builder
	err = h.templates.ExecuteTemplate(&buf, "file-tree.html", data)
	if err != nil {
		h.logger.Printf("erreur d execution du template de l arborescence pour OOB: %v", err)
		return // Don't fail the main request, just log
	}

	// Wrap it in a div with hx-swap-oob
	oobContent := fmt.Sprintf(`<div id="file-tree" hx-swap-oob="true">%s</div>`, buf.String())
	io.WriteString(w, oobContent)
}

// handleCreateFolder crée un nouveau dossier
func (h *Handler) handleCreateFolder(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "lecture du formulaire impossible", http.StatusBadRequest)
		return
	}

	folderPath := r.FormValue("path")
	if folderPath == "" {
		http.Error(w, "chemin du dossier manquant", http.StatusBadRequest)
		return
	}

	// Sécurité : nettoyer le chemin
	folderPath = filepath.Clean(folderPath)
	if strings.HasPrefix(folderPath, "..") || filepath.IsAbs(folderPath) {
		http.Error(w, "chemin invalide", http.StatusBadRequest)
		return
	}

	fullPath := filepath.Join(h.notesDir, folderPath)

	// Vérifier si le dossier existe déjà
	if _, err := os.Stat(fullPath); err == nil {
		http.Error(w, "un dossier avec ce nom existe deja", http.StatusConflict)
		return
	}

	// Créer le dossier
	if err := os.MkdirAll(fullPath, 0o755); err != nil {
		h.logger.Printf("erreur de creation du dossier %s: %v", folderPath, err)
		http.Error(w, "creation du dossier impossible", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("dossier cree: %s", folderPath)

	// Rafraîchir l'arborescence
	h.renderFileTreeOOB(w)
	io.WriteString(w, fmt.Sprintf("Dossier '%s' créé avec succès", folderPath))
}

// handleMoveFile déplace ou renomme un fichier/dossier
func (h *Handler) handleMoveFile(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "methode non supportee", http.StatusMethodNotAllowed)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "lecture du formulaire impossible", http.StatusBadRequest)
		return
	}

	sourcePath := r.FormValue("source")
	destPath := r.FormValue("destination")

	if sourcePath == "" || destPath == "" {
		http.Error(w, "chemins source et destination requis", http.StatusBadRequest)
		return
	}

	// Sécurité : nettoyer les chemins
	sourcePath = filepath.Clean(sourcePath)
	destPath = filepath.Clean(destPath)

	if strings.HasPrefix(sourcePath, "..") || strings.HasPrefix(destPath, "..") {
		http.Error(w, "chemins invalides", http.StatusBadRequest)
		return
	}

	sourceFullPath := filepath.Join(h.notesDir, sourcePath)
	destFullPath := filepath.Join(h.notesDir, destPath)

	// Vérifier que la source existe
	if _, err := os.Stat(sourceFullPath); os.IsNotExist(err) {
		http.Error(w, "fichier source introuvable", http.StatusNotFound)
		return
	}

	// Créer le répertoire de destination si nécessaire
	destDir := filepath.Dir(destFullPath)
	if err := os.MkdirAll(destDir, 0o755); err != nil {
		h.logger.Printf("erreur de creation du repertoire de destination %s: %v", destDir, err)
		http.Error(w, "creation du repertoire de destination impossible", http.StatusInternalServerError)
		return
	}

	// Déplacer le fichier
	if err := os.Rename(sourceFullPath, destFullPath); err != nil {
		h.logger.Printf("erreur de deplacement de %s vers %s: %v", sourcePath, destPath, err)
		http.Error(w, "deplacement impossible", http.StatusInternalServerError)
		return
	}

	h.logger.Printf("fichier deplace: %s -> %s", sourcePath, destPath)

	// Re-indexer
	go func() {
		if err := h.idx.Load(h.notesDir); err != nil {
			h.logger.Printf("echec de la reindexation post-deplacement: %v", err)
		}
	}()

	// Rafraîchir l'arborescence
	h.renderFileTreeOOB(w)
	io.WriteString(w, fmt.Sprintf("Fichier déplacé de '%s' vers '%s'", sourcePath, destPath))
}
