using System.ComponentModel;
using Kompas.Mcp.Core;
using Kompas.Mcp.Core.Query;
using ModelContextProtocol.Server;

namespace Kompas.Mcp.Host.Tools;

/// <summary>
/// Структурное «зрение» агента — чтение модели как данных, а не картинки.
/// Предпочтительнее снимка (<c>model_snapshot</c>): точнее и дешевле по контексту.
/// </summary>
[McpServerToolType]
public sealed class InspectionTools(KompasSession session, ModelInspectionService inspection)
{
    [McpServerTool(Name = "describe_model")]
    [Description("ПРЕДПОЧТИТЕЛЬНЫЙ способ «увидеть» активную деталь — структурный паспорт одним вызовом: " +
                 "габарит, МЦХ, тела, сводка топологии (грани/рёбра по типам), дерево построения с параметрами и переменные. " +
                 "Вызывайте ПЕРЕД model_snapshot: это точнее снимка и дешевле по контексту. Снимок берите только для " +
                 "визуально-пространственных вопросов, на которые паспорт не отвечает.")]
    public async Task<string> DescribeModel()
    {
        await session.ConnectAsync();
        var d = await inspection.DescribeModelAsync();
        return InspectionText.Render(d);
    }

    [McpServerTool(Name = "list_features")]
    [Description("Дерево построения активной детали по порядку: индекс, имя операции и ключевые параметры " +
                 "(глубина/радиус/катеты). Для импортированных моделей (STEP) истории нет — только начало координат и тело.")]
    public async Task<string> ListFeatures()
    {
        await session.ConnectAsync();
        var fs = await inspection.ListFeaturesAsync();
        if (fs.Count == 0) return "Дерево построения пусто.";
        var lines = fs.Select(f => $"  [{f.Index}] «{f.Name}»" + (string.IsNullOrEmpty(f.Detail) ? "" : $" — {f.Detail}"));
        return $"Операций: {fs.Count}\n" + string.Join("\n", lines);
    }

    [McpServerTool(Name = "list_bodies")]
    [Description("Тела активной детали: индекс, признак твёрдого тела и число граней. Полезно для многотельных деталей.")]
    public async Task<string> ListBodies()
    {
        await session.ConnectAsync();
        var bs = await inspection.ListBodiesAsync();
        if (bs.Count == 0) return "Тел нет (модель пуста?).";
        var lines = bs.Select(b => $"  [{b.Index}] {(b.IsSolid ? "твёрдое" : "поверхность")}, граней {b.FaceCount}");
        return $"Тел: {bs.Count}\n" + string.Join("\n", lines);
    }

    [McpServerTool(Name = "list_variables")]
    [Description("Переменные активной детали: имя, выражение, значение и признаки (внешняя/информационная). " +
                 "Это «ручки управления» параметрической модели.")]
    public async Task<string> ListVariables()
    {
        await session.ConnectAsync();
        var vs = await inspection.ListVariablesAsync();
        if (vs.Count == 0) return "Переменных нет.";
        var lines = vs.Select(v =>
        {
            var flags = (v.External ? " [внешняя]" : "") + (v.Information ? " [инфо]" : "");
            return Inv($"  {v.Name} = {v.Expression} (= {v.Value:G}){flags}");
        });
        return $"Переменных: {vs.Count}\n" + string.Join("\n", lines);
    }

    [McpServerTool(Name = "describe_face")]
    [Description("Детали грани по индексу из list_faces: тип, площадь, нормаль в центре, радиус (для цилиндра) " +
                 "и число ограничивающих рёбер. Углубляет list_faces без угадывания координат.")]
    public async Task<string> DescribeFace([Description("Индекс грани из list_faces")] int faceIndex)
    {
        await session.ConnectAsync();
        var f = await inspection.DescribeFaceAsync(faceIndex);
        var sb = Inv($"Грань [{f.Index}]: {f.Type}, площадь {f.Area:F2} мм², ограничивающих рёбер: {f.EdgeCount}");
        if (f.Radius is { } r) sb += Inv($", радиус {r:F2} мм");
        if (f.Normal is { } n) sb += Inv($", нормаль ({n.X:F3}, {n.Y:F3}, {n.Z:F3})");
        return sb;
    }

    [McpServerTool(Name = "describe_edge")]
    [Description("Детали ребра по индексу из list_edges: тип, длина, две смежные грани и координаты концевых вершин.")]
    public async Task<string> DescribeEdge([Description("Индекс ребра из list_edges")] int edgeIndex)
    {
        await session.ConnectAsync();
        var e = await inspection.DescribeEdgeAsync(edgeIndex);
        var sb = Inv($"Ребро [{e.Index}]: {e.Type}, длина {e.Length:F2} мм");
        if (e.AdjacentFace1 is not null || e.AdjacentFace2 is not null)
            sb += $", смежные грани: {e.AdjacentFace1 ?? "?"} / {e.AdjacentFace2 ?? "?"}";
        if (e.Start is { } s && e.End is { } en)
            sb += Inv($", концы ({s.X:F2}, {s.Y:F2}, {s.Z:F2}) → ({en.X:F2}, {en.Y:F2}, {en.Z:F2})");
        return sb;
    }

    [McpServerTool(Name = "measure")]
    [Description("Измерить расстояние и угол между двумя объектами активной детали. kind1/kind2: face|edge|vertex; " +
                 "индексы — из list_faces/list_edges. Возвращает минимальное расстояние (мм) и угол (°, если применим).")]
    public async Task<string> Measure(
        [Description("Тип первого объекта: face|edge|vertex")] string kind1,
        [Description("Индекс первого объекта")] int index1,
        [Description("Тип второго объекта: face|edge|vertex")] string kind2,
        [Description("Индекс второго объекта")] int index2)
    {
        await session.ConnectAsync();
        var m = await inspection.MeasureAsync(kind1, index1, kind2, index2);
        var angle = m.AngleValid ? Inv($", угол {m.Angle:F2}°") : " (угол неприменим)";
        return Inv($"{kind1}[{index1}] ↔ {kind2}[{index2}]: расстояние {m.MinDistance:F2} мм{angle}");
    }

    private static string Inv(FormattableString fs) => FormattableString.Invariant(fs);
}
