using System.Runtime.Versioning;
using Kompas6API5;
using Kompas6Constants;
using Kompas6Constants3D;

namespace Kompas.Mcp.Core.Modeling;

/// <summary>PartModeler: создание эскизов и добавление 2D-примитивов.</summary>
[SupportedOSPlatform("windows")]
public sealed partial class PartModeler
{
    /// <summary>Создать эскиз на базовой плоскости и открыть его на редактирование. Возвращает id эскиза.</summary>
    public Task<int> OpenSketchAsync(BasePlane plane, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var part = GetTopPart();
            var planeEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(plane)) as ksEntity
                ?? throw new InvalidOperationException("Не удалось получить базовую плоскость.");
            return CreateSketchOn(part, planeEntity);
        }, ct);

    /// <summary>
    /// Создать эскиз на плоской грани детали, найденной по точке <paramref name="x"/>,<paramref name="y"/>,<paramref name="z"/>
    /// (мировые координаты модели, мм), и открыть его на редактирование. Возвращает id эскиза.
    /// Это ключ к итеративному циклу «построил → выбрал грань → новый эскиз на ней».
    /// </summary>
    public Task<int> OpenSketchOnFaceAsync(double x, double y, double z, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var part = GetTopPart();
            var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
                ?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
            // Оставляем в коллекции только грани, проходящие через указанную точку.
            faces.SelectByPoint(x, y, z);
            if (faces.GetCount() == 0)
                throw new InvalidOperationException(
                    $"В точке ({x}, {y}, {z}) не найдено грани. Сделайте снимок и сверьте координаты.");

            var face = faces.GetByIndex(0) as ksEntity
                ?? throw new InvalidOperationException("Грань из коллекции не приводится к ksEntity.");
            return CreateSketchOn(part, face);
        }, ct);

    /// <summary>
    /// Создать эскиз на грани детали, заданной индексом из list_faces, и открыть на редактирование.
    /// Индекс стабилен, пока геометрия не меняется. Возвращает id эскиза.
    /// </summary>
    public Task<int> OpenSketchOnFaceIndexAsync(int faceIndex, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var part = GetTopPart();
            var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
                ?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
            if (faceIndex < 0 || faceIndex >= faces.GetCount())
                throw new ArgumentOutOfRangeException(nameof(faceIndex),
                    $"Индекс грани вне диапазона [0; {faces.GetCount() - 1}]. Сверьтесь с list_faces.");

            var face = faces.GetByIndex(faceIndex) as ksEntity
                ?? throw new InvalidOperationException("Грань по индексу не приводится к ksEntity.");
            return CreateSketchOn(part, face);
        }, ct);

    /// <summary>
    /// Создать смещённую плоскость (база + offset мм) и открыть на ней эскиз. Возвращает id эскиза.
    /// Снимает ограничение «только базовые плоскости»: эскиз на произвольной высоте/смещении.
    /// </summary>
    public Task<int> OpenSketchOnOffsetPlaneAsync(BasePlane basePlane, double offset, bool direction = true, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            if (!double.IsFinite(offset))
                throw new ArgumentException("Смещение должно быть конечным числом.", nameof(offset));
            var part = GetTopPart();
            var planeEntity = part.NewEntity((short)Obj3dType.o3d_planeOffset) as ksEntity
                ?? throw new InvalidOperationException("NewEntity(o3d_planeOffset) вернул null.");
            var pdef = planeEntity.GetDefinition() as ksPlaneOffsetDefinition
                ?? throw new InvalidOperationException("GetDefinition() плоскости вернул не ksPlaneOffsetDefinition.");
            var baseEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(basePlane)) as ksEntity
                ?? throw new InvalidOperationException("Не удалось получить базовую плоскость.");
            pdef.SetPlane(baseEntity);
            pdef.offset = offset;
            pdef.direction = direction;
            if (!planeEntity.Create())
                throw new InvalidOperationException("Create() смещённой плоскости вернул FALSE.");
            return CreateSketchOn(part, planeEntity);
        }, ct);

    /// <summary>Общее ядро: создать эскиз на заданной плоскости/грани и открыть на редактирование.</summary>
    private int CreateSketchOn(ksPart part, ksEntity planeEntity)
    {
        var entity = part.NewEntity((short)Obj3dType.o3d_sketch) as ksEntity
            ?? throw new InvalidOperationException("NewEntity(o3d_sketch) вернул null.");
        var def = entity.GetDefinition() as ksSketchDefinition
            ?? throw new InvalidOperationException("GetDefinition() эскиза вернул не ksSketchDefinition.");

        def.SetPlane(planeEntity);
        if (!entity.Create())
            throw new InvalidOperationException("Create() эскиза вернул FALSE.");

        var editor = def.BeginEdit() as ksDocument2D
            ?? throw new InvalidOperationException("BeginEdit() не вернул ksDocument2D.");

        var id = _nextId++;
        _sketches[id] = new SketchEntry { Entity = entity, Definition = def, Editor = editor };
        return id;
    }

    /// <summary>Добавить отрезок в открытый эскиз.</summary>
    public Task AddLineAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksLineSeg(x1, y1, x2, y2, MainLineStyle) == 0)
                throw new InvalidOperationException("ksLineSeg вернул 0 (отрезок не создан).");
        }, ct);

    /// <summary>Добавить окружность в открытый эскиз.</summary>
    public Task AddCircleAsync(int sketchId, double centerX, double centerY, double radius, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            if (radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius), "Радиус должен быть > 0.");
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksCircle(centerX, centerY, radius, MainLineStyle) == 0)
                throw new InvalidOperationException("ksCircle вернул 0 (окружность не создана).");
        }, ct);

    /// <summary>Добавить прямоугольник (4 отрезка) по двум углам.</summary>
    public Task AddRectangleAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var editor = RequireOpenSketch(sketchId).Editor!;
            void Seg(double ax, double ay, double bx, double by)
            {
                if (editor.ksLineSeg(ax, ay, bx, by, MainLineStyle) == 0)
                    throw new InvalidOperationException("ksLineSeg вернул 0 (сторона прямоугольника не создана).");
            }
            Seg(x1, y1, x2, y1);
            Seg(x2, y1, x2, y2);
            Seg(x2, y2, x1, y2);
            Seg(x1, y2, x1, y1);
        }, ct);

    /// <summary>Добавить осевую линию (стиль «осевая») в эскиз — ось для операции вращения.</summary>
    public Task AddAxisAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksLineSeg(x1, y1, x2, y2, AxisLineStyle) == 0)
                throw new InvalidOperationException("ksLineSeg (ось) вернул 0 (ось не создана).");
        }, ct);

    /// <summary>Завершить редактирование эскиза.</summary>
    public Task CloseSketchAsync(int sketchId, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() => CloseSketchCore(RequireSketch(sketchId)), ct);

    // ---- Новые примитивы (пакет A) -----------------------------------------

    private const short SplineOrder = 4; // порядок NURBS (степень+1); 4 = кубический сплайн
    private const int PointStyle = 0;    // системный стиль точки (см. SDK pstyles)

    /// <summary>Добавить дугу по трём точкам (начало, точка на дуге, конец) в открытый эскиз.</summary>
    public Task AddArc3PointsAsync(int sketchId, double x1, double y1, double x2, double y2, double x3, double y3, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksArcBy3Points(x1, y1, x2, y2, x3, y3, MainLineStyle) == 0)
                throw new InvalidOperationException("ksArcBy3Points вернул 0 (дуга не создана — три точки коллинеарны?).");
        }, ct);

    /// <summary>Добавить дугу по центру, радиусу и углам (градусы) в открытый эскиз.</summary>
    public Task AddArcByAngleAsync(int sketchId, double centerX, double centerY, double radius, double startAngle, double endAngle, bool counterClockwise = true, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            SketchGeometry.RequirePositive(radius, nameof(radius));
            var editor = RequireOpenSketch(sketchId).Editor!;
            var dir = SketchGeometry.ArcDirection(counterClockwise);
            if (editor.ksArcByAngle(centerX, centerY, radius, startAngle, endAngle, dir, MainLineStyle) == 0)
                throw new InvalidOperationException("ksArcByAngle вернул 0 (дуга не создана).");
        }, ct);

    /// <summary>Добавить эллипс: центр, полуоси A/B (мм), угол наклона большой оси к X (градусы).</summary>
    public Task AddEllipseAsync(int sketchId, double centerX, double centerY, double semiMajor, double semiMinor, double angle = 0, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            SketchGeometry.RequirePositive(semiMajor, nameof(semiMajor));
            SketchGeometry.RequirePositive(semiMinor, nameof(semiMinor));
            var editor = RequireOpenSketch(sketchId).Editor!;
            ksEllipseParam? p = null;
            try
            {
                p = NewParam<ksEllipseParam>(StructType2DEnum.ko_EllipseParam);
                // ВНИМАНИЕ: interop генерирует свойства A/B с ЗАГЛАВНОЙ (не a/b как в SDK-доках). Регистр в C# важен.
                p.xc = centerX; p.yc = centerY;
                p.A = semiMajor; p.B = semiMinor;
                p.angle = angle; p.style = MainLineStyle;
                if (editor.ksEllipse(p) == 0)
                    throw new InvalidOperationException("ksEllipse вернул 0 (эллипс не создан).");
            }
            finally { ReleaseCom(p); }
        }, ct);

    /// <summary>Добавить ломаную (цепочку отрезков) по списку точек. closed замыкает последнюю с первой.</summary>
    public Task AddPolylineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            // closed-ломаная из 2 точек не образует площадь — требуем минимум 3.
            SketchGeometry.RequirePoints(points, closed ? 3 : 2, nameof(points));
            var editor = RequireOpenSketch(sketchId).Editor!;
            for (int i = 0; i + 1 < points.Count; i++)
                Seg(editor, points[i], points[i + 1]);
            if (closed)
                Seg(editor, points[^1], points[0]);

            static void Seg(ksDocument2D ed, (double x, double y) a, (double x, double y) b)
            {
                if (ed.ksLineSeg(a.x, a.y, b.x, b.y, MainLineStyle) == 0)
                    throw new InvalidOperationException("ksLineSeg вернул 0 (сегмент ломаной не создан).");
            }
        }, ct);

    /// <summary>Добавить правильный многоугольник: центр, vertexCount вершин, radius (мм),
    /// inscribed=true — вершины на окружности (вписанный), false — стороны касаются (описанный),
    /// angle — поворот первой вершины (градусы).</summary>
    public Task AddPolygonAsync(int sketchId, double centerX, double centerY, int vertexCount, double radius, bool inscribed = true, double angle = 0, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            SketchGeometry.RequireVertexCount(vertexCount);
            SketchGeometry.RequirePositive(radius, nameof(radius));
            var editor = RequireOpenSketch(sketchId).Editor!;
            ksRegularPolygonParam? p = null;
            try
            {
                p = NewParam<ksRegularPolygonParam>(StructType2DEnum.ko_RegularPolygonParam);
                p.count = vertexCount; p.xc = centerX; p.yc = centerY;
                p.radius = radius; p.ang = angle;
                p.describe = SketchGeometry.PolygonDescribe(inscribed);
                p.style = MainLineStyle;
                if (editor.ksRegularPolygon(p, 0) == 0)
                    throw new InvalidOperationException("ksRegularPolygon вернул 0 (многоугольник не создан).");
            }
            finally { ReleaseCom(p); }
        }, ct);

    /// <summary>Добавить сплайн (кубический NURBS, порядок 4) через список точек. closed замыкает кривую.</summary>
    public Task AddSplineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            SketchGeometry.RequirePoints(points, 2, nameof(points));
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksNurbs(SplineOrder, closed, MainLineStyle) == 0)
                throw new InvalidOperationException("ksNurbs вернул 0 (сплайн не открыт).");
            // ksNurbs открыл составной объект — ksEndObj ОБЯЗАТЕЛЕН в любом исходе, иначе редактор «застрянет».
            var completed = false;
            try
            {
                foreach (var (x, y) in points)
                {
                    ksNurbsPointParam? np = null;
                    try
                    {
                        np = NewParam<ksNurbsPointParam>(StructType2DEnum.ko_NurbsPointParam);
                        np.x = x; np.y = y; np.weight = 1.0;
                        if (editor.ksNurbsPoint(np) == 0)
                            throw new InvalidOperationException("ksNurbsPoint вернул 0 (узел сплайна не добавлен).");
                    }
                    finally { ReleaseCom(np); }
                }
                completed = true;
            }
            finally
            {
                // Закрываем составной объект всегда; ошибку завершения сообщаем только на успешном пути,
                // чтобы не подменить исходное исключение из цикла.
                var end = editor.ksEndObj();
                if (completed && end == 0)
                    throw new InvalidOperationException("ksEndObj вернул 0 (сплайн не завершён).");
            }
        }, ct);

    /// <summary>Добавить точку в открытый эскиз (опорная точка для построений).</summary>
    public Task AddPointAsync(int sketchId, double x, double y, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            var editor = RequireOpenSketch(sketchId).Editor!;
            if (editor.ksPoint(x, y, PointStyle) == 0)
                throw new InvalidOperationException("ksPoint вернул 0 (точка не создана).");
        }, ct);
}
