package handlers

import (
	"net/http"
	"strconv"
	"strings"

	"mgit.msbls.de/m/paliad/internal/services"
)

// GET /api/tools/fristenrechner/search — unified search across the
// Fristenrechner concept layer (t-paliad-131 Phase C, t-paliad-133 v3
// extension). Returns at most `limit` concept cards, each with its
// proceeding pills.
//
// Query params:
//   q                   - free-text search (trigram + alias)
//   party               - filter by effective_party
//   proc                - filter by proceeding_type code
//   source              - filter by legal_source prefix
//   event_category_slug - v3 B1 narrowing; only concepts reachable
//                         from this taxonomy node and its descendants
//                         appear. Empty q is allowed when this is set
//                         (browse mode).
//   browse              - "all" enables v3 B1 entry mode: returns
//                         every concept mapped to any leaf of the
//                         decision tree (no narrowing, no query).
//                         Ignored when q is non-empty.
//   forum               - comma-separated v3 forum-bucket slugs
//                         (upc_cfi, upc_coa, de_lg, de_olg, de_bgh,
//                         de_bpatg, epa_grant, epa_opp, epa_appeal,
//                         dpma). Trigger pills bypass this filter.
//   limit               - max cards (default 12, max 30; in browse
//                         modes default 200, max 500)
//   kind                - "events" switches to the events-shape
//                         response (Fristenrechner overhaul S1,
//                         design §6.1). The default concept-card
//                         shape is unchanged when kind is empty.
//
// Returns an empty cards array (not 400) when q is empty — that lets
// the frontend boot the search input without a server round-trip.
func handleFristenrechnerSearch(w http.ResponseWriter, r *http.Request) {
	if dbSvc == nil || dbSvc.deadlineSearch == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Suche vorübergehend nicht verfügbar (keine Datenbank).",
		})
		return
	}
	if r.URL.Query().Get("kind") == "events" {
		handleFristenrechnerSearchEvents(w, r)
		return
	}
	q := r.URL.Query().Get("q")
	opts := services.SearchOptions{
		Party:             r.URL.Query().Get("party"),
		Proc:              r.URL.Query().Get("proc"),
		Source:            r.URL.Query().Get("source"),
		EventCategorySlug: r.URL.Query().Get("event_category_slug"),
		Forums:            parseCSV(r.URL.Query().Get("forum")),
		BrowseAll:         r.URL.Query().Get("browse") == "all",
		Limit:             parseLimit(r.URL.Query().Get("limit")),
	}
	resp, err := dbSvc.deadlineSearch.Search(r.Context(), q, opts)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "Suche fehlgeschlagen: " + err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// handleFristenrechnerSearchEvents serves the ?kind=events shape of
// /api/tools/fristenrechner/search (overhaul S1, design §6.1). Returns
// one hit per (procedural_event × proceeding_type) tuple, with a
// follow-up count and a trigram similarity score.
//
// Query params (additive to the legacy search params):
//   q             - free-text search against name / name_en / code
//   jurisdiction  - "UPC" | "DE" | "EPA" | "DPMA"
//   proc          - proceeding_type code
//   event_kind    - "filing" | "hearing" | "decision" | "order"
//   party         - primary_party of the anchor rule
//   limit         - max hits (default 50, max 200)
func handleFristenrechnerSearchEvents(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query().Get("q")
	opts := services.EventSearchOptions{
		Jurisdiction:       strings.ToUpper(strings.TrimSpace(r.URL.Query().Get("jurisdiction"))),
		ProceedingTypeCode: r.URL.Query().Get("proc"),
		EventKind:          r.URL.Query().Get("event_kind"),
		PrimaryParty:       r.URL.Query().Get("party"),
		Limit:              parseLimit(r.URL.Query().Get("limit")),
	}
	resp, err := dbSvc.deadlineSearch.SearchEvents(r.Context(), q, opts)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "Ereignis-Suche fehlgeschlagen: " + err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

// parseCSV splits a comma-separated query-string value into a slice of
// trimmed non-empty entries. Empty input → nil.
func parseCSV(raw string) []string {
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	out := make([]string, 0, len(parts))
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, p)
		}
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func parseLimit(raw string) int {
	if raw == "" {
		return 0
	}
	n, err := strconv.Atoi(raw)
	if err != nil || n < 0 {
		return 0
	}
	return n
}
