// Package hooks provides hook utilities for claude-mnemonic.
package hooks

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"
)

// HookResponse is the response sent back to Claude Code.
type HookResponse struct {
	Continue bool `json:"continue"`
}

// ProjectIDWithName returns both the hash ID and the directory name for display.
// Format: "dirname_abc123" (name + truncated hash for human-readability)
func ProjectIDWithName(cwd string) string {
	absPath, err := filepath.Abs(cwd)
	if err != nil {
		absPath = cwd
	}

	dirName := filepath.Base(absPath)
	hash := sha256.Sum256([]byte(absPath))
	shortHash := hex.EncodeToString(hash[:3]) // 6 chars

	return fmt.Sprintf("%s_%s", dirName, shortHash)
}

// HookDeadline returns a context with the hook's timeout budget minus a safety margin.
// This ensures hooks return gracefully before Claude kills them.
func HookDeadline(timeout time.Duration) (context.Context, context.CancelFunc) {
	// Use 80% of the timeout to leave margin for response serialization
	safeTimeout := time.Duration(float64(timeout) * 0.8)
	return context.WithTimeout(context.Background(), safeTimeout)
}

// Exit codes for Claude Code hooks
const (
	ExitSuccess         = 0
	ExitFailure         = 1
	ExitUserMessageOnly = 3 // Display stderr as user message
)

// WriteResponse writes a hook response to stdout.
func WriteResponse(hookName string, success bool) {
	response := HookResponse{Continue: success}
	data, _ := json.Marshal(response)
	fmt.Println(string(data))
}

// WriteError writes an error message to stderr and exits.
func WriteError(hookName string, err error) {
	fmt.Fprintf(os.Stderr, "[%s] Error: %v\n", hookName, err)
	WriteResponse(hookName, false)
}

// BaseInput contains common fields shared by all hook inputs.
type BaseInput struct {
	SessionID      string `json:"session_id"`
	CWD            string `json:"cwd"`
	PermissionMode string `json:"permission_mode"`
	HookEventName  string `json:"hook_event_name"`
}

// HookContext provides common context for hook handlers.
type HookContext struct {
	HookName  string
	Project   string
	SessionID string
	CWD       string
	RawInput  []byte
	Port      int
}

// HookHandler is a function that handles hook-specific logic.
// It receives the context and returns an optional context string and error.
type HookHandler[T any] func(ctx *HookContext, input *T) (additionalContext string, err error)

// RunHook executes a hook with common boilerplate handling.
// It handles: internal call skip, stdin reading, JSON unmarshaling,
// worker startup, and project ID generation.
func RunHook[T any](hookName string, handler HookHandler[T]) {
	// Skip if this is an internal call (from SDK processor)
	if os.Getenv("CLAUDE_MNEMONIC_INTERNAL") == "1" {
		WriteResponse(hookName, true)
		return
	}

	// Read input from stdin
	inputData, err := io.ReadAll(os.Stdin)
	if err != nil {
		WriteError(hookName, err)
		os.Exit(1)
	}

	// Parse input
	var input T
	if err = json.Unmarshal(inputData, &input); err != nil {
		WriteError(hookName, err)
		os.Exit(1)
	}

	// Ensure worker is running
	port, err := EnsureWorkerRunning()
	if err != nil {
		WriteError(hookName, err)
		os.Exit(1)
	}

	// Extract base fields using interface assertion or reflection
	var base BaseInput
	_ = json.Unmarshal(inputData, &base)

	// Generate project ID from CWD
	project := ProjectIDWithName(base.CWD)

	// Create context
	ctx := &HookContext{
		HookName:  hookName,
		Port:      port,
		Project:   project,
		SessionID: base.SessionID,
		CWD:       base.CWD,
		RawInput:  inputData,
	}

	// Run hook-specific handler
	additionalContext, err := handler(ctx, &input)
	if err != nil {
		WriteError(hookName, err)
		os.Exit(1)
	}

	// Output response
	if additionalContext != "" {
		response := map[string]interface{}{
			"continue": true,
			"hookSpecificOutput": map[string]interface{}{
				"hookEventName":     hookName,
				"additionalContext": additionalContext,
			},
		}
		_ = json.NewEncoder(os.Stdout).Encode(response)
		os.Exit(0)
	}

	WriteResponse(hookName, true)
}

// StatuslineHandler is a function that handles statusline-specific logic.
// It receives input and port, returns formatted status string.
// No context injection or worker startup - just display.
type StatuslineHandler[T any] func(input *T, port int) string

// RunStatuslineHook executes a statusline hook with minimal overhead.
// Unlike RunHook, this:
// - Does NOT check CLAUDE_MNEMONIC_INTERNAL (statuslines always run)
// - Uses GetWorkerPort() instead of EnsureWorkerRunning() (no startup)
// - Prints output directly to stdout (no JSON wrapping)
// This keeps statusline fast (<100ms requirement).
func RunStatuslineHook[T any](handler StatuslineHandler[T]) {
	// Read input from stdin
	inputData, err := io.ReadAll(os.Stdin)
	if err != nil {
		// On error, handler receives nil and should return offline status
		fmt.Println(handler(nil, 0))
		return
	}

	// Parse input
	var input T
	if err := json.Unmarshal(inputData, &input); err != nil {
		// On parse error, handler receives nil and should return offline status
		fmt.Println(handler(nil, 0))
		return
	}

	// Get worker port (does NOT start worker)
	port := GetWorkerPort()

	// Run handler and print result
	fmt.Println(handler(&input, port))
}
