using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;

namespace AxCopilot.Handlers;

/// <summary>
/// L19-1: 공학 계산기 핸들러. "calc" 프리픽스로 사용합니다.
///
/// 예: calc                   → 사용법 목록
///     calc sin 45            → sin(45°) = 0.7071
///     calc cos 60            → cos(60°) = 0.5
///     calc tan 45            → tan(45°) = 1.0
///     calc sqrt 144          → √144 = 12
///     calc log 1000          → log₁₀(1000) = 3
///     calc ln 2.718          → ln(2.718) ≈ 1.0
///     calc pow 2 10          → 2¹⁰ = 1024
///     calc factorial 10      → 10! = 3628800
///     calc gcd 12 18         → GCD(12,18) = 6
///     calc lcm 4 6           → LCM(4,6) = 12
///     calc pi                → π = 3.14159265358979
///     calc e                 → e = 2.71828182845905
///     calc deg 1.5707        → 라디안 → 도 변환
///     calc rad 90            → 도 → 라디안 변환
///     calc abs -42           → 절댓값
///     calc ceil 3.2          → 올림
///     calc floor 3.8         → 내림
///     calc round 3.567 2     → 반올림 (소수점 자리)
/// Enter → 결과 복사.
/// </summary>
public class CalcHandler : IActionHandler
{
    public string? Prefix => "calc";

    public PluginMetadata Metadata => new(
        "Calc",
        "공학 계산기 — sin·cos·log·sqrt·factorial·GCD·LCM 등",
        "1.0",
        "AX");

    public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
    {
        var q     = query.Trim();
        var items = new List<LauncherItem>();

        if (string.IsNullOrWhiteSpace(q))
        {
            items.Add(new LauncherItem("공학 계산기",
                "calc sin/cos/tan/sqrt/log/ln/pow/factorial/gcd/lcm/pi/e/deg/rad/abs/ceil/floor/round",
                null, null, Symbol: "\uE8EF"));
            items.Add(BuildUsage("삼각함수",       "calc sin 45  /  calc cos 60  /  calc tan 30"));
            items.Add(BuildUsage("제곱근·거듭제곱", "calc sqrt 144  /  calc pow 2 10"));
            items.Add(BuildUsage("로그",           "calc log 1000  /  calc ln 2.718"));
            items.Add(BuildUsage("팩토리얼",       "calc factorial 12"));
            items.Add(BuildUsage("GCD · LCM",     "calc gcd 12 18  /  calc lcm 4 6"));
            items.Add(BuildUsage("상수",           "calc pi  /  calc e"));
            items.Add(BuildUsage("단위 변환",      "calc deg 1.5707  /  calc rad 90"));
            items.Add(BuildUsage("기타",           "calc abs -42  /  calc ceil 3.2  /  calc floor 3.8  /  calc round 3.567 2"));
            return Task.FromResult<IEnumerable<LauncherItem>>(items);
        }

        var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        var fn    = parts[0].ToLowerInvariant();

        // 단순 상수
        if (fn is "pi")  { var v = Math.PI;  return Result(items, "π (Pi)",  v, "π"); }
        if (fn is "e")   { var v = Math.E;   return Result(items, "e (자연상수)", v, "e"); }
        if (fn is "phi") { var v = 1.6180339887; return Result(items, "φ (황금비)", v, "φ"); }

        // 단일 인수 함수
        if (parts.Length >= 2 && double.TryParse(parts[1],
                System.Globalization.NumberStyles.Float,
                System.Globalization.CultureInfo.InvariantCulture, out var a))
        {
            switch (fn)
            {
                case "sin":
                    return Result(items, $"sin({a}°)", Math.Sin(DegToRad(a)), "sin");

                case "cos":
                    return Result(items, $"cos({a}°)", Math.Cos(DegToRad(a)), "cos");

                case "tan":
                    if (Math.Abs(a % 180 - 90) < 1e-9)
                    {
                        items.Add(ErrorItem("tan(90°±n·180°)는 정의되지 않습니다 (무한대)"));
                        return Task.FromResult<IEnumerable<LauncherItem>>(items);
                    }
                    return Result(items, $"tan({a}°)", Math.Tan(DegToRad(a)), "tan");

                case "asin":
                    if (a < -1 || a > 1) { items.Add(ErrorItem("asin 입력값은 -1 ~ 1 범위여야 합니다")); break; }
                    return Result(items, $"asin({a}) °", RadToDeg(Math.Asin(a)), "asin", isAngle: true);

                case "acos":
                    if (a < -1 || a > 1) { items.Add(ErrorItem("acos 입력값은 -1 ~ 1 범위여야 합니다")); break; }
                    return Result(items, $"acos({a}) °", RadToDeg(Math.Acos(a)), "acos", isAngle: true);

                case "atan":
                    return Result(items, $"atan({a}) °", RadToDeg(Math.Atan(a)), "atan", isAngle: true);

                case "sqrt":
                    if (a < 0) { items.Add(ErrorItem("음수의 실수 제곱근은 정의되지 않습니다")); break; }
                    return Result(items, $"√{a}", Math.Sqrt(a), "sqrt");

                case "cbrt":
                    return Result(items, $"∛{a}", Math.Cbrt(a), "cbrt");

                case "log":
                    if (a <= 0) { items.Add(ErrorItem("log 입력값은 양수여야 합니다")); break; }
                    return Result(items, $"log₁₀({a})", Math.Log10(a), "log10");

                case "log2":
                    if (a <= 0) { items.Add(ErrorItem("log2 입력값은 양수여야 합니다")); break; }
                    return Result(items, $"log₂({a})", Math.Log2(a), "log2");

                case "ln":
                    if (a <= 0) { items.Add(ErrorItem("ln 입력값은 양수여야 합니다")); break; }
                    return Result(items, $"ln({a})", Math.Log(a), "ln");

                case "exp":
                    return Result(items, $"e^{a}", Math.Exp(a), "exp");

                case "factorial":
                {
                    var n = (long)Math.Round(a);
                    if (n < 0 || n > 20) { items.Add(ErrorItem("팩토리얼은 0~20 범위만 지원합니다")); break; }
                    var fac = Factorial(n);
                    items.Add(new LauncherItem($"{n}! = {fac:N0}", $"팩토리얼  · Enter 복사",
                        null, ("copy", $"{fac}"), Symbol: "\uE8EF"));
                    items.Add(new LauncherItem("결과", $"{fac}", null, ("copy", $"{fac}"), Symbol: "\uE8EF"));
                    return Task.FromResult<IEnumerable<LauncherItem>>(items);
                }

                case "abs":
                    return Result(items, $"|{a}|", Math.Abs(a), "abs");

                case "ceil":
                    return Result(items, $"⌈{a}⌉", Math.Ceiling(a), "ceil");

                case "floor":
                    return Result(items, $"⌊{a}⌋", Math.Floor(a), "floor");

                case "round":
                {
                    int decimals = 0;
                    if (parts.Length >= 3 && int.TryParse(parts[2], out var d)) decimals = Math.Clamp(d, 0, 15);
                    var rounded = Math.Round(a, decimals, MidpointRounding.AwayFromZero);
                    return Result(items, $"round({a}, {decimals})", rounded, "round");
                }

                case "deg":
                    return Result(items, $"{a} rad → °", RadToDeg(a), "deg", isAngle: true);

                case "rad":
                    return Result(items, $"{a}° → rad", DegToRad(a), "rad");

                case "sign":
                    items.Add(new LauncherItem($"sign({a}) = {Math.Sign(a)}",
                        a > 0 ? "양수" : a < 0 ? "음수" : "0",
                        null, ("copy", $"{Math.Sign(a)}"), Symbol: "\uE8EF"));
                    return Task.FromResult<IEnumerable<LauncherItem>>(items);
            }
        }

        // 이인수 함수
        if (parts.Length >= 3 &&
            double.TryParse(parts[1], System.Globalization.NumberStyles.Float,
                System.Globalization.CultureInfo.InvariantCulture, out var x) &&
            double.TryParse(parts[2], System.Globalization.NumberStyles.Float,
                System.Globalization.CultureInfo.InvariantCulture, out var y))
        {
            switch (fn)
            {
                case "pow":
                    return Result(items, $"{x}^{y}", Math.Pow(x, y), "pow");

                case "log":
                    if (x <= 0 || y <= 0 || y == 1) { items.Add(ErrorItem("밑과 진수는 양수, 밑 ≠ 1 이어야 합니다")); break; }
                    return Result(items, $"log_({x}) {y}", Math.Log(y, x), "log");

                case "atan2":
                    return Result(items, $"atan2({x},{y}) °", RadToDeg(Math.Atan2(x, y)), "atan2", isAngle: true);

                case "hypot":
                    return Result(items, $"hypot({x},{y})", Math.Sqrt(x * x + y * y), "hypot");

                case "gcd":
                {
                    var ga = (long)Math.Abs(Math.Round(x));
                    var gb = (long)Math.Abs(Math.Round(y));
                    var gv = GcdLong(ga, gb);
                    items.Add(new LauncherItem($"GCD({ga}, {gb}) = {gv}", "최대공약수  · Enter 복사",
                        null, ("copy", $"{gv}"), Symbol: "\uE8EF"));
                    items.Add(new LauncherItem("결과", $"{gv}", null, ("copy", $"{gv}"), Symbol: "\uE8EF"));
                    return Task.FromResult<IEnumerable<LauncherItem>>(items);
                }

                case "lcm":
                {
                    var la = (long)Math.Abs(Math.Round(x));
                    var lb = (long)Math.Abs(Math.Round(y));
                    var g  = GcdLong(la, lb);
                    var lv = g == 0 ? 0 : la / g * lb;
                    items.Add(new LauncherItem($"LCM({la}, {lb}) = {lv}", "최소공배수  · Enter 복사",
                        null, ("copy", $"{lv}"), Symbol: "\uE8EF"));
                    items.Add(new LauncherItem("결과", $"{lv}", null, ("copy", $"{lv}"), Symbol: "\uE8EF"));
                    return Task.FromResult<IEnumerable<LauncherItem>>(items);
                }

                case "mod":
                    if (y == 0) { items.Add(ErrorItem("0으로 나눌 수 없습니다")); break; }
                    return Result(items, $"{x} mod {y}", x % y, "mod");
            }
        }

        // 파싱 실패 안내
        items.Add(new LauncherItem($"알 수 없는 함수: '{fn}'",
            "calc sin/cos/tan/sqrt/log/ln/pow/factorial/gcd/lcm/abs/ceil/floor/round/deg/rad",
            null, null, Symbol: "\uE783"));
        return Task.FromResult<IEnumerable<LauncherItem>>(items);
    }

    public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
    {
        if (item.Data is ("copy", string text))
        {
            try
            {
                System.Windows.Application.Current.Dispatcher.Invoke(
                    () => Clipboard.SetText(text));
                NotificationService.Notify("Calc", "클립보드에 복사했습니다.");
            }
            catch { }
        }
        return Task.CompletedTask;
    }

    // ── 헬퍼 ─────────────────────────────────────────────────────────────────

    private static Task<IEnumerable<LauncherItem>> Result(
        List<LauncherItem> items, string label, double value, string fn, bool isAngle = false)
    {
        var formatted = FormatDouble(value);
        var extra     = isAngle ? " °" : "";
        items.Add(new LauncherItem($"{label} = {formatted}{extra}",
            $"· Enter 복사", null, ("copy", formatted), Symbol: "\uE8EF"));
        items.Add(new LauncherItem("결과", $"{formatted}{extra}", null, ("copy", formatted), Symbol: "\uE8EF"));
        // 추가 정보
        if (!isAngle && value != 0)
        {
            items.Add(new LauncherItem("과학적 표기법", $"{value:E6}", null, ("copy", $"{value:E6}"), Symbol: "\uE8EF"));
            if (value > 0)
                items.Add(new LauncherItem("log₁₀ 값", $"{Math.Log10(value):F6}", null, null, Symbol: "\uE8EF"));
        }
        return Task.FromResult<IEnumerable<LauncherItem>>(items);
    }

    private static string FormatDouble(double v)
    {
        if (double.IsNaN(v)) return "NaN";
        if (double.IsPositiveInfinity(v)) return "+∞";
        if (double.IsNegativeInfinity(v)) return "-∞";
        // 정수라면 소수점 없이
        if (v == Math.Floor(v) && Math.Abs(v) < 1e15)
            return $"{(long)v}";
        return $"{v:G15}";
    }

    private static LauncherItem BuildUsage(string title, string example) =>
        new(title, example, null, null, Symbol: "\uE8EF");

    private static LauncherItem ErrorItem(string msg) =>
        new(msg, "올바른 입력 형식을 확인하세요", null, null, Symbol: "\uE783");

    private static double DegToRad(double deg) => deg * Math.PI / 180.0;
    private static double RadToDeg(double rad) => rad * 180.0 / Math.PI;

    private static long Factorial(long n)
    {
        long r = 1;
        for (long i = 2; i <= n; i++) r *= i;
        return r;
    }

    private static long GcdLong(long a, long b) => b == 0 ? a : GcdLong(b, a % b);
}
