// Package httpresponse builds the API envelope per api-design.md §3-§4.
package httpresponse

import (
	"encoding/json"
	"net/http"
)

// Pagination mirrors the Laravel paginator shape expected by the Vue frontend.
type Pagination struct {
	CurrentPage int `json:"current_page"`
	PerPage     int `json:"per_page"`
	Total       int `json:"total"`
	LastPage    int `json:"last_page"`
}

// Meta is the success envelope meta object.
type Meta struct {
	Pagination *Pagination `json:"pagination,omitempty"`
	RequestID  string      `json:"request_id"`
}

// OK writes {"data": v, "meta": {...}} with no pagination.
func OK(w http.ResponseWriter, status int, requestID string, data any) {
	writeJSON(w, status, map[string]any{
		"data": data,
		"meta": Meta{RequestID: requestID},
	})
}

// OKPaginated writes a collection + pagination meta.
func OKPaginated(w http.ResponseWriter, status int, requestID string, data any, page, perPage, total int) {
	OKPaginatedWithMeta(w, status, requestID, data, page, perPage, total, nil)
}

// OKPaginatedWithMeta writes a paginated collection and merges additional
// collection metadata alongside pagination and request_id.
func OKPaginatedWithMeta(w http.ResponseWriter, status int, requestID string, data any, page, perPage, total int, extra map[string]any) {
	lastPage := 0
	if perPage > 0 {
		lastPage = (total + perPage - 1) / perPage
	}
	meta := map[string]any{
		"pagination": Pagination{
			CurrentPage: page,
			PerPage:     perPage,
			Total:       total,
			LastPage:    lastPage,
		},
		"request_id": requestID,
	}
	for key, value := range extra {
		meta[key] = value
	}
	writeJSON(w, status, map[string]any{
		"data": data,
		"meta": meta,
	})
}

// ErrorCode is the stable machine-readable code on the wire.
type ErrorCode string

const (
	CodeValidation        ErrorCode = "VALIDATION_ERROR"
	CodeAuthentication    ErrorCode = "AUTHENTICATION_REQUIRED"
	CodeForbidden         ErrorCode = "FORBIDDEN"
	CodeEmailNotVerified  ErrorCode = "EMAIL_NOT_VERIFIED"
	CodeAccountDisabled   ErrorCode = "ACCOUNT_DISABLED"
	CodeNotFound          ErrorCode = "NOT_FOUND"
	CodeConflict          ErrorCode = "CONFLICT"
	CodeBusinessViolation ErrorCode = "BUSINESS_RULE_VIOLATION"
	CodeRateLimited       ErrorCode = "RATE_LIMITED"
	CodeInternal          ErrorCode = "INTERNAL_ERROR"

	// MsgInternal is the generic client-visible message for 500s.
	MsgInternal = "Something went wrong"
)

type errorBody struct {
	Code    ErrorCode           `json:"code"`
	Message string              `json:"message"`
	Details map[string][]string `json:"details,omitempty"`
}

// Error writes the error envelope. For internal errors the message is always
// generic; details are only ever logged server-side.
func Error(w http.ResponseWriter, status int, requestID string, code ErrorCode, message string, details map[string][]string) {
	writeJSON(w, status, map[string]any{
		"error": errorBody{Code: code, Message: message, Details: details},
	})
}

// Internal logs the real error and returns a generic message to the client.
type ErrorLogger interface {
	Error(msg string, args ...any)
}

func Internal(w http.ResponseWriter, requestID string, log ErrorLogger, err error) {
	log.Error("internal_error", "err", err)
	Error(w, http.StatusInternalServerError, requestID, CodeInternal, MsgInternal, nil)
}

func writeJSON(w http.ResponseWriter, status int, body any) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(body); err != nil {
		// nothing else we can do; the response header is already committed
		return
	}
}
