/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 */
package router

import (
	"fmt"
	"net/http"
	"regexp"
)

type MiddlewareFunc func(next Handler) Handler

type Handler interface {
	ServeHTTP(rctx *Context)
}

type HandlerFunc func(rctx *Context)

func (handlerFunc HandlerFunc) ServeHTTP(rctx *Context) {
	handlerFunc(rctx)
}

type Router struct {
	headHandler  Handler
	routeHandler *Selector
}

func NewRouter() *Router {
	selector := NewSelector()
	return &Router{
		routeHandler: selector,
		headHandler:  selector,
	}
}

func (rout *Router) Use(mwFunc MiddlewareFunc) {
	rout.headHandler = mwFunc(rout.headHandler)
}

func (rout *Router) Selector() *Selector {
	return rout.routeHandler
}

func (rout *Router) AddRoute(method, path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute(method, path, handlerFunc)
}

func (rout *Router) Head(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("HEAD", path, handlerFunc)
}

func (rout *Router) Get(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("GET", path, handlerFunc)
}

func (rout *Router) Post(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("POST", path, handlerFunc)
}

func (rout *Router) Put(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("PUT", path, handlerFunc)
}

func (rout *Router) Patch(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("PATCH", path, handlerFunc)
}

func (rout *Router) Delete(path string, handlerFunc HandlerFunc) {
	rout.routeHandler.AddRoute("DELETE", path, handlerFunc)
}

func (rout *Router) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
	rctx := NewContext(writer, req)
	rout.headHandler.ServeHTTP(rctx)
}

func (rout *Router) NotFound(handlerFunc HandlerFunc) {
	rout.routeHandler.notFound = handlerFunc
}

type Selector struct {
	Routes   []*Route
	notFound Handler
}

func NewSelector() *Selector {
	notFound := HandlerFunc(func(ctx *Context) {
		http.NotFound(ctx.Writer, ctx.Request)
	})
	return &Selector{
		Routes:   make([]*Route, 0),
		notFound: notFound,
	}
}

const globalRoutePattern = `^%s$`

func (hand *Selector) AddRoute(method, path string, handlerFunc HandlerFunc) error {
	var err error
	rawPath := path
	path, err = pathCompiler(path)
	if err != nil {
		return err
	}

	path = fmt.Sprintf(globalRoutePattern, path)
	re, err := regexp.Compile(path)
	if err != nil {
		return err
	}

	route := &Route{
		Method:  method,
		Path:    path,
		RawPath: rawPath,
		Handler: handlerFunc,
		Regexp:  re,
	}
	hand.Routes = append(hand.Routes, route)
	return err
}

func (hand *Selector) ServeHTTP(rctx *Context) {
	realHandler := hand.notFound
	for _, route := range hand.Routes {
		match := route.Match(rctx.Request)
		if !match {
			continue
		}
		subvals := route.Regexp.FindStringSubmatch(rctx.Request.URL.Path)
		subkeys := route.Regexp.SubexpNames()
		for i, val := range subvals {
			key := subkeys[i]
			if key != "" {
				rctx.PathMap[key] = val
			}
		}
		realHandler = route.Handler
		break
	}
	realHandler.ServeHTTP(rctx)
}

type Route struct {
	Method  string
	Regexp  *regexp.Regexp
	Path    string
	RawPath string
	Handler HandlerFunc
}

func (route Route) Match(req *http.Request) bool {
	if req.Method != route.Method {
		return false
	}
	match := route.Regexp.MatchString(req.URL.Path)
	if match {
		return true
	}
	return false
}
