using System.Globalization;
using System.Runtime.Versioning;
using Kompas.Mcp.Core.Threading;
using Kompas6API5;
using Kompas6Constants3D;

namespace Kompas.Mcp.Core.Modeling;

/// <summary>
/// Параметрика: создание и изменение переменных модели (API5 <c>ksVariable</c>).
/// Создание возможно только на коллекции переменных корневого <c>ksFeature</c> (не <c>ksPart</c>).
/// <para>ВАЖНО: переменная управляет геометрией лишь в параметрической модели (где размеры
/// эскизов связаны с именованными переменными). Эскизы, построенные литеральными координатами,
/// не имеют именованных размеров — для них переменные хранятся, но геометрию не меняют.</para>
/// </summary>
[SupportedOSPlatform("windows")]
public sealed class VariableService
{
    private readonly KompasSession _session;
    private readonly KompasDispatcher _dispatcher;

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

    /// <summary>Создать новую переменную модели с начальным значением. external=true — внешняя
    /// (доступна для управления из сборки). Возвращает вычисленное значение.</summary>
    public Task<double> CreateVariableAsync(string name, double value, string? note = null, bool external = false, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            if (!double.IsFinite(value)) throw new ArgumentException("Значение должно быть конечным.", nameof(value));
            var (part, vc) = RootVariables();
            if (vc.GetByName(name, true, false) is ksVariable)
                throw new InvalidOperationException($"Переменная '{name}' уже существует.");
            var v = vc.AddNewVariable(name, value, note ?? "") as ksVariable
                ?? throw new InvalidOperationException("AddNewVariable вернул null (недопустимое имя?).");
            if (external) v.external = true;
            if (!part.RebuildModel())
                throw new InvalidOperationException($"Не удалось применить создание переменной '{name}' (RebuildModel вернул FALSE).");
            return ReadValueFresh(name); // значение перечитываем после перестроения
        }, ct);

    /// <summary>Задать выражение переменной (константа «30» или формула «len*2+5») и перестроить.
    /// Возвращает вычисленное значение.</summary>
    public Task<double> SetVariableAsync(string name, string expression, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            if (string.IsNullOrWhiteSpace(expression))
                throw new ArgumentException("Выражение не может быть пустым.", nameof(expression));
            var (part, vc) = RootVariables();
            var v = RequireVariable(vc, name);
            v.Expression = expression;
            if (!part.RebuildModel())
                throw new InvalidOperationException(
                    $"Не удалось пересчитать модель с выражением «{expression}» для '{name}' (недопустимое выражение или ссылка на несуществующую переменную?).");
            return ReadValueFresh(name); // переменную перечитываем после перестроения (value пересчитан)
        }, ct);

    /// <summary>
    /// Задать выражение переменной, создав её при отсутствии (upsert) — за один заход на STA-поток.
    /// Избавляет от разведочного вызова «а есть ли уже такая»: агент просто задаёт нужное значение.
    /// Новая переменная заводится с нулём и сразу получает выражение (оно может быть и формулой).
    /// Возвращает вычисленное значение и признак создания.
    /// </summary>
    public Task<(double Value, bool Created)> SetOrCreateVariableAsync(
        string name, string expression, string? note = null, bool external = false, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            if (string.IsNullOrWhiteSpace(expression))
                throw new ArgumentException("Выражение не может быть пустым.", nameof(expression));

            var (part, vc) = RootVariables();
            var existing = vc.GetByName(name, true, false) as ksVariable;
            var created = existing is null;

            var v = existing ?? (vc.AddNewVariable(name, 0, note ?? "") as ksVariable
                ?? throw new InvalidOperationException("AddNewVariable вернул null (недопустимое имя?)."));

            v.Expression = expression;
            if (!created && note is not null) v.note = note;
            if (external) v.external = true;

            if (!part.RebuildModel())
                throw new InvalidOperationException(
                    $"Не удалось пересчитать модель с выражением «{expression}» для '{name}' (недопустимое выражение или ссылка на несуществующую переменную?).");
            return (ReadValueFresh(name), created);
        }, ct);

    /// <summary>Удалить переменную модели.</summary>
    public Task DeleteVariableAsync(string name, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            var (part, vc) = RootVariables();
            RequireVariable(vc, name); // понятная ошибка, если её нет
            if (!vc.RemoveVariable(name))
                throw new InvalidOperationException(
                    $"Не удалось удалить переменную '{name}' (возможно, на неё ссылаются другие переменные — удалите зависимые сначала).");
            if (!part.RebuildModel())
                throw new InvalidOperationException($"Не удалось применить удаление переменной '{name}' (RebuildModel вернул FALSE).");
        }, ct);

    /// <summary>Прочитать текущее (вычисленное) значение переменной.</summary>
    public Task<double> GetVariableValueAsync(string name, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            var (_, vc) = RootVariables();
            return RequireVariable(vc, name).value;
        }, ct);

    /// <summary>Заменить комментарий (note) существующей переменной.</summary>
    public Task SetVariableNoteAsync(string name, string note, CancellationToken ct = default)
        => _dispatcher.InvokeAsync(() =>
        {
            RequireName(name);
            var (_, vc) = RootVariables();
            var v = RequireVariable(vc, name);
            v.note = note;
        }, ct);

    /// <summary>Перечитать значение переменной из свежей коллекции (после RebuildModel оно пересчитано).</summary>
    private double ReadValueFresh(string name)
    {
        var (_, vc) = RootVariables();
        return RequireVariable(vc, name).value;
    }

    private static void RequireName(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Имя переменной не может быть пустым.", nameof(name));
    }

    private static ksVariable RequireVariable(ksVariableCollection vc, string name)
        => vc.GetByName(name, true, false) as ksVariable
           ?? throw new KeyNotFoundException($"Переменная '{name}' не найдена.");

    /// <summary>Корневой ksFeature детали и его коллекция переменных (на ней работает AddNewVariable).</summary>
    private (ksPart part, ksVariableCollection vc) RootVariables()
    {
        var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
            ?? throw new InvalidOperationException("Нет активного 3D-документа.");
        var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
            ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
        var feature = part.GetFeature() as ksFeature
            ?? throw new InvalidOperationException("GetFeature() вернул null (нет дерева построения).");
        var vc = feature.VariableCollection as ksVariableCollection
            ?? throw new InvalidOperationException("VariableCollection вернул не ksVariableCollection.");
        return (part, vc);
    }
}
