package builtin

// convert.go — birim dönüştürme araçları.
//
// Neden ayrı dosya? CATIA/teknik çalışmalarda birim dönüştürme SÜREKLİ gerekir
// (mm ↔ inç, °C ↔ °F, kg ↔ lb...). Dönüşüm "sabit katsayılı çarpma" olduğu için
// güvenlik riski yoktur; yanlışlık riski ise YANLI SAYI üretmektir — o yüzden
// her sonuç kaynağı ve hedefiyle birlikte döner (kullanıcı doğrulayabilir).
//
// Kritik kural: FARKLI BOYUTLAR (mm → kg) reddedilir. Matematiksel olarak bir sayı
// üretilebilir ama ANLAMSIZDIR ve kullanıcıyı yanıltır — sessizce sayı vermek yerine
// hata vermek gerekir.

import (
	"context"
	"fmt"
	"math"
	"sort"
	"strings"

	"fiko-ai/internal/tool"
)

// unitDef, bir birimin taban birime çarpanını ve görünen adını taşır.
type unitDef struct {
	Factor float64 // 1 birim kaç taban birime karşılık gelir
	Name   string  // kullanıcıya gösterilen ad
}

// unitGroup, aynı boyuttaki birimlerin toplandığı grup.
type unitGroup struct {
	Base  string
	Units map[string]unitDef
}

// unitGroups, desteklenen tüm boyutlar. Sıcaklık AÇIKÇA işaretlenmiştir:
// kaydırmalı (affine) dönüşüm olduğu için çarpma katsayısı YETMEZ.
var unitGroups = map[string]unitGroup{
	"uzunluk": {
		Base: "m",
		Units: map[string]unitDef{
			"mm": {1e-3, "milimetre"}, "cm": {1e-2, "santimetre"},
			"m": {1, "metre"}, "km": {1e3, "kilometre"},
			"in": {0.0254, "inç"}, "ft": {0.3048, "feet"},
			"yd": {0.9144, "yard"}, "mil": {1609.344, "mil"},
			"um": {1e-6, "mikrometre"},
		},
	},
	"kutle": {
		Base: "kg",
		Units: map[string]unitDef{
			"mg": {1e-6, "miligram"}, "g": {1e-3, "gram"},
			"kg": {1, "kilogram"}, "t": {1000, "ton"},
			"oz": {0.028349523125, "ons"}, "lb": {0.45359237, "pound"},
			"stone": {6.35029318, "stone"},
		},
	},
	"sicaklik": {
		Base: "C",
		// Factor ALAN IZLEMEZ: sicaklik kaydirmalidir (affine). Birim kimligi
		// ayrica sicaklikUnit ile tutulur; yoksa C (factor 1) ile K (factor 1)
		// ayirt edilemez ve 98.6 F -> 119.88 C gibi YANLIS sonuc cikar.
		Units: map[string]unitDef{
			"C": {0, "Santigrat"}, "F": {0, "Fahrenheit"},
			"K": {0, "Kelvin"},
		},
	},
	"alan": {
		Base: "m2",
		Units: map[string]unitDef{
			"mm2": {1e-6, "milimetre²"}, "cm2": {1e-4, "santimetre²"},
			"m2": {1, "metre²"}, "km2": {1e6, "kilometre²"},
			"in2": {6.4516e-4, "inç²"}, "ft2": {0.09290304, "feet²"},
		},
	},
	"hacim": {
		Base: "L",
		Units: map[string]unitDef{
			"ml": {0.001, "mililitre"}, "l": {1, "litre"},
			"m3": {1000, "metreküp"}, "cm3": {0.001, "santimetreküp"},
			"gal": {3.785411784, "galon (ABD)"}, "qt": {0.946352946, "quart"},
		},
	},
	"hiz": {
		Base: "kmh",
		Units: map[string]unitDef{
			"kmh": {1, "km/saat"}, "ms": {3.6, "m/saat"},
			"mph": {1.609344, "mil/saat"}, "kn": {1.852, "deniz mili/saat"},
		},
	},
	"aci": {
		Base: "deg",
		Units: map[string]unitDef{
			"deg": {1, "derece"}, "rad": {57.29577951308232, "radyan"},
			"grad": {0.9, "gradyan"},
		},
	},
}

// convertDefinitions, birim dönüştürme tool'larını üretir.
func convertDefinitions(_ Options) []Def {
	return []Def{
		{
			Enabled: true,
			Tool: tool.Tool{
				Name:        "unit.convert",
				Title:       "Birim dönüştür",
				Description: "Uzunluk, kütle, sıcaklık, alan, hacim, hız ve açı birimlerini çevirir (mm↔inç, kg↔lb, °C↔°F ...). Sıcaklık kaydırmalıdır, çarpımsal değil.",
				Tags:        []string{"unit"},
				Risk:        tool.RiskLow,
				Permissions: []string{"execute"},
				InputSchema: obj("type", "object",
					"properties", obj(
						"value", obj("type", "number", "description", "Dönüştürülecek sayı"),
						"from", str("Kaynak birim (örn. mm, kg, C)"),
						"to", str("Hedef birim (örn. in, lb, F)"),
					),
					"required", []any{"value", "from", "to"},
				),
				OutputSchema: obj("type", "object", "properties", obj(
					"value", obj("type", "number"),
					"from", str("Kaynak birim"),
					"to", str("Hedef birim"),
					"readable", str("İnsan okunur sonuç"),
				)),
				UI:        tool.UIForm,
				TimeoutMs: 5_000,
			},
			Handler: convertHandler,
		},
		{
			Enabled: true,
			Tool: tool.Tool{
				Name:        "unit.list",
				Title:       "Birimleri listele",
				Description: "Desteklenen birim gruplarını ve her gruptaki birimleri döner.",
				Tags:        []string{"unit"},
				Risk:        tool.RiskLow,
				Permissions: []string{"execute"},
				InputSchema: obj("type", "object",
					"properties", obj("group", str("Grup adı (boşsa hepsi)")),
				),
				OutputSchema: obj("type", "object", "properties", obj(
					"groups", obj("type", "array", "items", obj("type", "object")),
				)),
				UI:        tool.UITable,
				TimeoutMs: 5_000,
			},
			Handler: convertListHandler,
		},
	}
}

// normalizeUnit, birim adını küçük harfe indirir ve yaygın yazımları kabul eder.
// ("°C" → "c", "inç" → "in", "metre" → "m"): kullanıcı Türkçe yazınca hata almasın.
func normalizeUnit(s string) string {
	s = strings.ToLower(strings.TrimSpace(s))
	s = strings.ReplaceAll(s, "²", "2")
	switch s {
	case "°c", "c", "celcius", "santigrat", "santigrad":
		return "C"
	case "°f", "f", "fahrenheit":
		return "F"
	case "k", "kelvin":
		return "K"
	case "inch", "inç", "inc", "inches":
		return "in"
	case "litre", "liter", "lt":
		return "l"
	case "ton", "tonne", "tonna":
		return "t"
	case "metre", "meter":
		return "m"
	case "milimetre", "millimeter":
		return "mm"
	case "santimetre", "centimeter":
		return "cm"
	case "kilogram", "kilo":
		return "kg"
	case "gram":
		return "g"
	case "pound", "paund":
		return "lb"
	case "derece", "degree", "°":
		return "deg"
	case "radyan", "radian":
		return "rad"
	case "km/s", "kmh", "km/saat":
		return "kmh"
	case "m/s", "ms", "m/saat":
		return "ms"
	}
	return s
}

// findUnit, birimi tüm gruplarda arar ve ait olduğu grubu döner.
func findUnit(name string) (string, unitGroup, unitDef, bool) {
	n := normalizeUnit(name)
	for gname, g := range unitGroups {
		if u, ok := g.Units[n]; ok {
			// Sicaklikta birim kimligi KATSAYIDAN gelmez (hepsi 0); adini dondur.
			if gname == "sicaklik" {
				u.Name = n
			}
			return gname, g, u, true
		}
	}
	return "", unitGroup{}, unitDef{}, false
}
func convertHandler(_ context.Context, t *tool.Tool, in map[string]any) (map[string]any, error) {
	value, ok := toFloat(in["value"])
	if !ok {
		return nil, Fail(t.Name, "value sayı olmalı")
	}
	fromRaw, _ := in["from"].(string)
	toRaw, _ := in["to"].(string)
	if strings.TrimSpace(fromRaw) == "" || strings.TrimSpace(toRaw) == "" {
		return nil, Fail(t.Name, "from ve to zorunludur")
	}

	fg, _, fu, ok := findUnit(fromRaw)
	if !ok {
		return nil, Fail(t.Name, "bilinmeyen birim: %q (unit.list ile bakabilirsin)", fromRaw)
	}
	tg, _, tu, ok := findUnit(toRaw)
	if !ok {
		return nil, Fail(t.Name, "bilinmeyen birim: %q (unit.list ile bakabilirsin)", toRaw)
	}
	// Farklı boyutlar (mm → kg) ANLAMSIZDIR. Sessizce sayı üretmek kullanıcıyı
	// yanıltır; reddetmek gerekir.
	if fg != tg {
		return nil, Fail(t.Name, "birimler farklı boyutta: %q (%s) -> %q (%s)", fromRaw, fg, toRaw, tg)
	}

	result := roundUnit(convertValue(value, fu, tu, fg))
	return map[string]any{
		"value":    result,
		"from":     fromRaw,
		"to":       toRaw,
		"readable": fmt.Sprintf("%g %s = %g %s", value, fromRaw, result, toRaw),
	}, nil
}

// convertValue gerçek dönüşümü yapar.
//
// SICAKLIK ÖZELDİR: C→F "×9/5 + 32", K→F "×9/5 − 459.67" gibi KAYDIRMA içerir.
// Basit çarpma burada YANLIŞ verir (0 °C → 0 °F çıkar) — o yüzden ayrı yol var.
func convertValue(v float64, from, to unitDef, group string) float64 {
	var base float64
	if group == "sicaklik" {
		switch from.Name {
		case "F":
			base = (v - 32) * 5.0 / 9.0
		case "K":
			base = v - 273.15
		default: // "C" zaten °C tabandir
			base = v
		}
	} else {
		base = v * from.Factor
	}

	var out float64
	if group == "sicaklik" {
		switch to.Name {
		case "F":
			out = base*9.0/5.0 + 32
		case "K":
			out = base + 273.15
		default: // "C"
			out = base
		}
	} else {
		out = base / to.Factor
	}
	return out
}

// roundUnit sonucu 10 basamağa indirger (0.1+0.2 gibi artıkları gizler).
func roundUnit(v float64) float64 {
	r := math.Round(v*1e10) / 1e10
	if r == 0 {
		return 0
	}
	return r
}

// convertListHandler, desteklenen grupları ve birimleri döner.
func convertListHandler(_ context.Context, _ *tool.Tool, in map[string]any) (map[string]any, error) {
	want, _ := in["group"].(string)
	want = strings.ToLower(strings.TrimSpace(want))

	names := make([]string, 0, len(unitGroups))
	for g := range unitGroups {
		names = append(names, g)
	}
	sort.Strings(names) // kararlı çıktı

	groups := []map[string]any{}
	for _, g := range names {
		if want != "" && want != g {
			continue
		}
		def := unitGroups[g]
		units := make([]string, 0, len(def.Units))
		for u, d := range def.Units {
			units = append(units, fmt.Sprintf("%s (%s)", u, d.Name))
		}
		sort.Strings(units)
		groups = append(groups, map[string]any{
			"group": g,
			"base":  def.Base,
			"units": strings.Join(units, ", "),
		})
	}
	if len(groups) == 0 {
		return nil, Fail("unit.list", "bilinmeyen grup; geçerli: %s", strings.Join(names, ", "))
	}
	return map[string]any{"groups": groups, "count": len(groups)}, nil
}
