using System.Runtime.Versioning;
using Kompas.Mcp.Core.Threading;
using KompasAPI7;

namespace Kompas.Mcp.Core.Editing;

/// <summary>
/// Прямое редактирование тела: перемещение грани (КОМПАС «Переместить грань», <c>FaceMover</c>).
/// Работает и на импортированной «тупой» B-rep геометрии без дерева построения.
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class FaceEditService
{
    private readonly KompasSession _session;
    private readonly KompasDispatcher _dispatcher;

    public FaceEditService(KompasSession session, KompasDispatcher dispatcher)
    {
        _session = session ?? throw new ArgumentNullException(nameof(session));
        _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
    }

    /// <summary>
    /// Переместить грань активной детали, найденную по мировой точке (<paramref name="x"/>,<paramref name="y"/>,<paramref name="z"/>),
    /// на <paramref name="distance"/> мм вдоль её нормали. Направление подбирается автоматически
    /// (наружу — добавление материала); если КОМПАС отвергает направление, пробуется обратное.
    /// </summary>
    public Task MoveFaceAsync(double x, double y, double z, double distance, string? name = null, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() => MoveFaceCore(x, y, z, distance, name), ct);

    private void MoveFaceCore(double x, double y, double z, double distance, string? name)
    {
        if (distance == 0) throw new ArgumentException("Смещение не может быть нулевым.", nameof(distance));

        if (_session.Application.ActiveDocument is not IKompasDocument3D doc3d)
            throw new InvalidOperationException("Активный документ не является 3D-документом.");
        if (doc3d.TopPart is not IPart7 top)
            throw new InvalidOperationException("Не удалось получить TopPart активного документа.");

        var face = FindFaceAtPoint(top, x, y, z)
            ?? throw new InvalidOperationException(
                $"В точке ({x}, {y}, {z}) не найдена грань. Сверьте координаты точки на грани (model_snapshot).");

        var sc = top as ISurfaceContainer
            ?? throw new InvalidOperationException("Деталь не приводится к ISurfaceContainer (FaceMover недоступен).");

        var fm = sc.FaceMovers.Add()
            ?? throw new InvalidOperationException("FaceMovers.Add() вернул null.");
        if (!fm.SetFaces(new object[] { face }, true))
            throw new InvalidOperationException("FaceMover.SetFaces отверг грань.");
        fm.Offset = Math.Abs(distance);
        // Имя ставим до Update(): иначе в дереве останется системное.
        if (Modeling.FeatureName.Normalize(name) is { } title && fm is IModelObject named) named.Name = title;

        // Направление: пробуем одно, при отказе — обратное (нормаль грани может смотреть в любую сторону).
        fm.Direction = distance > 0;
        if (!fm.Update())
        {
            fm.Direction = !fm.Direction;
            if (!fm.Update())
                throw new InvalidOperationException("FaceMover.Update вернул FALSE для обоих направлений.");
        }

        // Перестраиваем, чтобы состояние ошибки операции стало актуальным (проверяется через validate_part).
        doc3d.RebuildDocument();
    }

    private static IFace? FindFaceAtPoint(IPart7 part, double x, double y, double z)
    {
        var hit = part.FindObjectsByPoint(x, y, z, true);
        switch (hit)
        {
            case IFace f:
                return f;
            case object[] arr:
                foreach (var o in arr) if (o is IFace face) return face;
                return null;
            case System.Collections.IEnumerable en:
                foreach (var o in en) if (o is IFace face) return face;
                return null;
            default:
                return null;
        }
    }
}
