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

namespace Kompas.Mcp.Host.Tools;

/// <summary>Инструменты запросов к модели.</summary>
[McpServerToolType]
public sealed class QueryTools(KompasSession session, QueryService query, ModelInspectionService inspection)
{
    [McpServerTool(Name = "list_components")]
    [Description("Перечислить компоненты верхнего уровня активной сборки: индекс, имя, обозначение, признак детали и габарит (мм). Индекс/имя — для последующего извлечения или осмотра детали.")]
    public async Task<string> ListComponents()
    {
        await session.ConnectAsync();
        var comps = await query.ListComponentsAsync();
        if (comps.Count == 0) return "Компонентов нет (активный документ не сборка или пуст).";
        var lines = comps.Select(c =>
        {
            var mark = string.IsNullOrWhiteSpace(c.Marking) ? "" : $", обозн. \"{c.Marking}\"";
            return $"  [{c.Index}] {(c.IsDetail ? "деталь" : "узел ")} \"{c.Name}\", " +
                   $"габ {c.SizeX:F1}×{c.SizeY:F1}×{c.SizeZ:F1} мм{mark}";
        });
        return $"Компонентов: {comps.Count}\n" + string.Join("\n", lines);
    }

    [McpServerTool(Name = "list_faces")]
    [Description("Грани активной детали. Без index — список: индекс, тип поверхности " +
                 "(plane/cylinder/cone/sphere/torus/nurbs) и площадь (мм²). С index — подробности этой грани: " +
                 "площадь, нормаль в центре, радиус (для цилиндра) и число ограничивающих рёбер. " +
                 "Индекс — надёжный способ адресовать грань (sketch_create, hole, move_face, shell, draft) " +
                 "без угадывания координат.")]
    public async Task<string> ListFaces(
        [Description("Индекс грани для подробностей; пусто — список всех граней")] int? index = null)
    {
        await session.ConnectAsync();

        if (index is { } i)
        {
            var f = await inspection.DescribeFaceAsync(i);
            var detail = Inv($"Грань [{f.Index}]: {f.Type}, площадь {f.Area:F2} мм², ограничивающих рёбер: {f.EdgeCount}");
            if (f.Radius is { } r) detail += Inv($", радиус {r:F2} мм");
            if (f.Normal is { } n) detail += Inv($", нормаль ({n.X:F3}, {n.Y:F3}, {n.Z:F3})");
            return detail;
        }

        var faces = await query.ListFacesAsync();
        if (faces.Count == 0) return "Граней нет (модель пуста?).";
        var lines = faces.Select(f => $"  [{f.Index}] {f.Type}, площадь {f.Area:F2} мм²");
        return $"Граней: {faces.Count}\n" + string.Join("\n", lines);
    }

    [McpServerTool(Name = "list_edges")]
    [Description("Рёбра активной детали. Без index — список: индекс, тип кривой " +
                 "(line/circle/arc/ellipse/nurbs) и длина (мм). С index — подробности этого ребра: длина, " +
                 "две смежные грани и координаты концевых вершин. Индексы передавайте в fillet_edge / " +
                 "chamfer_edge — это надёжнее выбора по точке.")]
    public async Task<string> ListEdges(
        [Description("Индекс ребра для подробностей; пусто — список всех рёбер")] int? index = null)
    {
        await session.ConnectAsync();

        if (index is { } i)
        {
            var e = await inspection.DescribeEdgeAsync(i);
            var detail = Inv($"Ребро [{e.Index}]: {e.Type}, длина {e.Length:F2} мм");
            if (e.AdjacentFace1 is not null || e.AdjacentFace2 is not null)
                detail += $", смежные грани: {e.AdjacentFace1 ?? "?"} / {e.AdjacentFace2 ?? "?"}";
            if (e.Start is { } s && e.End is { } en)
                detail += Inv($", концы ({s.X:F2}, {s.Y:F2}, {s.Z:F2}) → ({en.X:F2}, {en.Y:F2}, {en.Z:F2})");
            return detail;
        }

        var edges = await query.ListEdgesAsync();
        if (edges.Count == 0) return "Рёбер нет (модель пуста?).";
        var lines = edges.Select(e => $"  [{e.Index}] {e.Type}, длина {e.Length:F2} мм");
        return $"Рёбер: {edges.Count}\n" + string.Join("\n", lines);
    }

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