using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace OpenCVStudio_v0
{
    public sealed class BlockLibraryController : IDisposable
    {
        private const int BlockLeft = 12; private const int BlockTop = 12; private const int BlockSpacing = 10; private const int BlockHeight = 72;
        private readonly TreeView treeViewBlocks; private readonly Panel panelPipeline; private readonly PropertyGrid? propertyGridBlock; private readonly ProjectManager projectManager;
        private readonly List<BlockDescriptor> availableBlocks = new();
        private PipelineBlockControl? selectedControl; private bool disposed;
        public event EventHandler<ProjectBlockEventArgs>? BlockAdded; public event EventHandler<ProjectBlockEventArgs>? BlockSelected; public event EventHandler<ProjectBlockEventArgs>? PreviewRequested;
        public BlockLibraryController(TreeView treeViewBlocks, Panel panelPipeline, ProjectManager projectManager, PropertyGrid? propertyGridBlock = null)
        {
            this.treeViewBlocks = treeViewBlocks ?? throw new ArgumentNullException(nameof(treeViewBlocks));
            this.panelPipeline = panelPipeline ?? throw new ArgumentNullException(nameof(panelPipeline));
            this.projectManager = projectManager ?? throw new ArgumentNullException(nameof(projectManager));
            this.propertyGridBlock = propertyGridBlock;
            ConfigureControls();
            CreateBlockLibrary();
            FillTreeView();
            this.treeViewBlocks.NodeMouseDoubleClick += TreeViewBlocks_NodeMouseDoubleClick;
            this.panelPipeline.Resize += PanelPipeline_Resize;
            this.projectManager.ProjectChanged += ProjectManager_ProjectChanged;
        }
        public IReadOnlyList<BlockDescriptor> AvailableBlocks => availableBlocks;
        private void CreateBlockLibrary()
        {
            availableBlocks.Clear();
            RegisterInputOutputBlocks();
            RegisterColorChannelBlocks();
            RegisterImageGeometryBlocks();
            RegisterFilteringBlocks();
            RegisterGradientBlocks();
            RegisterThresholdBlocks();
            RegisterMorphologyBlocks();
            RegisterSingleImageMathBlocks();
            RegisterTwoImageMathBlocks();
            RegisterHistogramBlocks();
            RegisterStatisticsBlocks();
            RegisterContourBlocks();
            RegisterContourGeometryBlocks();
            RegisterSegmentationBlocks();
            RegisterRegistrationBlocks();
            RegisterGeometricTransformBlocks();
            RegisterMultiImageBlocks();
            RegisterDiagnosticBlocks();
            availableBlocks.Sort((left, right) =>
            {
                int categoryComparison = left.Category.CompareTo(right.Category);
                if (categoryComparison != 0) return categoryComparison;
                return string.Compare(left.DisplayName, right.DisplayName, StringComparison.CurrentCultureIgnoreCase);
            });
        }
        private void RegisterBlock(string blockType, string displayName, string description, BlockCategory category, BlockDataType inputType, BlockDataType outputType, bool isImplemented = false, bool allowMultiple = true, bool canMove = true, bool hasFileSelection = false, SourceImageSlot sourceSlot = SourceImageSlot.None)
        {
            BlockDescriptor descriptor = new BlockDescriptor(blockType: blockType, displayName: displayName, description: description, category: category, inputType: inputType, outputType: outputType, isImplemented: isImplemented, allowMultiple: allowMultiple, canMove: canMove, hasFileSelection: hasFileSelection, sourceSlot: sourceSlot);
            availableBlocks.Add(descriptor);
        }
        private List<ProjectBlock> GetOrderedProjectBlocks()
        {
            if (projectManager.CurrentProject == null)
            {
                return new List<ProjectBlock>();
            }
            return projectManager.CurrentProject.Blocks.OrderBy(block => block.Order).ToList();
        }
        private bool CanMoveBlock(ProjectBlock movingBlock, int direction)
        {
            ArgumentNullException.ThrowIfNull(movingBlock);
            if (direction != -1 && direction != 1)
            {
                throw new ArgumentOutOfRangeException(nameof(direction), direction, "Направление должно быть -1 или 1.");
            }
            BlockDescriptor? movingDescriptor = FindDescriptor(movingBlock.BlockType);
            if (movingDescriptor == null)
            {
                MessageBox.Show($"Не найдено описание блока «{movingBlock.BlockType}».", "Ошибка перемещения", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            /*
             * Сам ImageSource или SecondImageSource
             * перемещать запрещено.
             */
            if (!movingDescriptor.CanMove)
            {
                MessageBox.Show("Данный блок нельзя перемещать.", "Перемещение блока", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            List<ProjectBlock> orderedBlocks = GetOrderedProjectBlocks();
            int currentIndex = orderedBlocks.FindIndex(block => block.Id == movingBlock.Id);
            if (currentIndex < 0)
            {
                MessageBox.Show("Перемещаемый блок не найден в проекте.", "Ошибка перемещения", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            int targetIndex = currentIndex + direction;
            /*
             * Выход за границы списка.
             * Обычно сюда не попадём, поскольку кнопка
             * крайнего обычного блока отключена.
             */
            if (targetIndex < 0 || targetIndex >= orderedBlocks.Count)
            {
                return false;
            }
            ProjectBlock targetBlock = orderedBlocks[targetIndex];
            BlockDescriptor? targetDescriptor = FindDescriptor(targetBlock.BlockType);
            /*
             * Обычный блок нельзя поменять местами
             * с ImageSource или SecondImageSource.
             */
            if (targetDescriptor?.IsImageSource == true)
            {
                MessageBox.Show("Через блок источника нельзя переместить блок.", "Перемещение блока", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return false;
            }
            return true;
        }
        private BlockDescriptor? FindDescriptor(string blockType)
        {
            if (string.IsNullOrWhiteSpace(blockType))
            {
                return null;
            }
            return availableBlocks.FirstOrDefault(descriptor => string.Equals(descriptor.BlockType, blockType, StringComparison.OrdinalIgnoreCase));
        }

        private int? GetBlockInsertIndex(BlockDescriptor descriptor)
        {
            ArgumentNullException.ThrowIfNull(descriptor);
            if (descriptor.SourceSlot == SourceImageSlot.Main)
            {
                return 0;
            }
            if (descriptor.SourceSlot == SourceImageSlot.Second)
            {
                /*
                 * Второй источник всегда располагается
                 * сразу после основного источника.
                 *
                 * Проверка наличия Main уже выполняется
                 * в ValidateBlockCanBeAdded().
                 */
                return 1;
            }
            /*
             * Обычные блоки добавляются в конец.
             */
            return null;
        }
        private void ValidateBlockCanBeAdded(BlockDescriptor descriptor)
        {
            ArgumentNullException.ThrowIfNull(descriptor);
            if (!projectManager.HasProject || projectManager.CurrentProject == null)
            {
                throw new InvalidOperationException("Сначала создайте или откройте проект.");
            }
            if (!descriptor.IsImplemented)
            {
                throw new NotSupportedException($"Блок «{descriptor.DisplayName}» пока добавлен только в каталог.\nИсполнитель этого блока ещё не реализован.");
            }
            if (!descriptor.AllowMultiple)
            {
                bool alreadyExists = projectManager.CurrentProject.Blocks.Any(block => string.Equals(block.BlockType, descriptor.BlockType, StringComparison.OrdinalIgnoreCase));
                if (alreadyExists)
                {
                    throw new InvalidOperationException($"Блок «{descriptor.DisplayName}» уже добавлен в проект.");
                }
            }
            if (descriptor.SourceSlot == SourceImageSlot.Second && !ContainsSource(SourceImageSlot.Main))
            {
                throw new InvalidOperationException("Сначала добавьте блок основного изображения.");
            }
        }
        private bool ContainsSource(SourceImageSlot sourceSlot)
        {
            if (sourceSlot == SourceImageSlot.None)
            {
                return false;
            }
            if (projectManager.CurrentProject == null)
            {
                return false;
            }
            return projectManager.CurrentProject.Blocks.Any(block =>
            {
                BlockDescriptor? descriptor = FindDescriptor(block.BlockType);
                return descriptor?.SourceSlot == sourceSlot;
            });
        }

        public void FillTreeView()
        {
            treeViewBlocks.BeginUpdate();
            try
            {
                treeViewBlocks.Nodes.Clear();
                IEnumerable<IGrouping<BlockCategory, BlockDescriptor>> groups = availableBlocks.OrderBy(block => block.Category).ThenBy(block => block.DisplayName).GroupBy(block => block.Category);
                foreach (IGrouping<BlockCategory, BlockDescriptor> group in groups)
                {
                    TreeNode categoryNode = new TreeNode { Text = BlockCategoryNames.GetName(group.Key), Name = group.Key.ToString(), Tag = group.Key };
                    foreach (BlockDescriptor descriptor in group)
                    {
                        TreeNode blockNode = new TreeNode { Text = descriptor.ToString(), Name = descriptor.BlockType, Tag = descriptor, ToolTipText = descriptor.Description };
                        if (!descriptor.IsImplemented) blockNode.ForeColor = Color.DimGray;
                        categoryNode.Nodes.Add(blockNode);
                    }
                    treeViewBlocks.Nodes.Add(categoryNode);
                }
                treeViewBlocks.ExpandAll();
            }
            finally { treeViewBlocks.EndUpdate(); }
        }
        public ProjectBlock? AddSelectedBlock()
        {
            if (treeViewBlocks.SelectedNode?.Tag is not BlockDescriptor descriptor) return null;
            return AddBlock(descriptor);
        }

        public ProjectBlock AddBlock(BlockDescriptor descriptor)
        {
            ValidateBlockCanBeAdded(descriptor);
            ProjectBlock? projectBlock = null;
            try
            {
                int? insertIndex = GetBlockInsertIndex(descriptor);
                projectBlock = projectManager.AddBlock(blockType: descriptor.BlockType, blockName: descriptor.DisplayName, insertIndex: insertIndex);

                AddDefaultParameters(projectBlock);
                AddVisualBlock(projectBlock);
                ArrangePipelineBlocks();
                SelectBlock(projectBlock.Id);
                BlockAdded?.Invoke(this, new ProjectBlockEventArgs(projectBlock));
                return projectBlock;
            }
            catch
            {
                /*
                 * Если блок уже был добавлен в модель проекта,
                 * но последующее создание параметров или UI
                 * завершилось ошибкой, удаляем незавершённый блок.
                 */
                if (projectBlock != null)
                {
                    projectManager.RemoveBlock(projectBlock.Id);
                    PipelineBlockControl? control = panelPipeline.Controls.OfType<PipelineBlockControl>().FirstOrDefault(item => item.ProjectBlock.Id == projectBlock.Id);
                    if (control != null)
                    {
                        panelPipeline.Controls.Remove(control);
                        control.Dispose();
                    }
                }
                throw;
            }
        }
        public void ReloadPipeline()
        {
            ClearPipelineControls();
            if (projectManager.CurrentProject == null) { propertyGridBlock?.SelectedObject = null; return; }
            IEnumerable<ProjectBlock> blocks = projectManager.CurrentProject.Blocks.OrderBy(block => block.Order);
            foreach (ProjectBlock block in blocks) AddVisualBlock(block);
            ArrangePipelineBlocks();
        }
        public void SelectBlock(Guid blockId)
        {
            PipelineBlockControl? control = panelPipeline.Controls.OfType<PipelineBlockControl>().FirstOrDefault(item => item.ProjectBlock.Id == blockId);
            if (control == null) return;
            SelectVisualControl(control);
        }
        public bool RemoveSelectedBlock()
        {
            if (selectedControl == null) return false;
            Guid blockId = selectedControl.ProjectBlock.Id;
            bool removed = projectManager.RemoveBlock(blockId);
            if (!removed) return false;
            panelPipeline.Controls.Remove(selectedControl);
            selectedControl.Dispose();
            selectedControl = null;
            propertyGridBlock?.SelectedObject = null;
            RefreshVisualBlockOrder();
            ArrangePipelineBlocks();
            return true;
        }
        public bool MoveSelectedBlockUp()
        {
            if (selectedControl == null)
            {
                return false;
            }
            ProjectBlock movingBlock = selectedControl.ProjectBlock;
            if (!CanMoveBlock(movingBlock, direction: -1))
            {
                return false;
            }
            Guid blockId = movingBlock.Id;
            if (!projectManager.MoveBlockUp(blockId))
            {
                return false;
            }
            /*
             * Не удаляем и не создаём контролы заново.
             * ProjectBlock уже получил новый Order.
             */
            RefreshVisualBlockOrder();
            ArrangePipelineBlocks();
            SelectBlock(blockId);
            return true;
        }
        public bool MoveSelectedBlockDown()
        {
            if (selectedControl == null)
            {
                return false;
            }
            ProjectBlock movingBlock = selectedControl.ProjectBlock;
            if (!CanMoveBlock(movingBlock, direction: 1))
            {
                return false;
            }
            Guid blockId = movingBlock.Id;
            if (!projectManager.MoveBlockDown(blockId))
            {
                return false;
            }
            RefreshVisualBlockOrder();
            ArrangePipelineBlocks();
            SelectBlock(blockId);
            return true;
        }
        private void AddDefaultParameters(ProjectBlock block)
        {
            BlockDescriptor? descriptor = FindDescriptor(block.BlockType);
            /*
             * Каждый источник хранит собственный путь
             * к изображению в параметрах блока.
             */
            if (descriptor?.HasFileSelection == true)
            {
                projectManager.SetBlockParameter(block.Id, "ImageRelativePath", string.Empty);
            }

            switch (block.BlockType)
            {
                case "ColorChannel":
                    projectManager.SetBlockParameter(block.Id, "ColorSpace", ColorSpaceMode.BGR);
                    projectManager.SetBlockParameter(block.Id, "Channel", 0);
                    break;
                case "Blur":
                    projectManager.SetBlockParameter(block.Id, "BlurType", BlurTypeMode.Gaussian);
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 5);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 5);
                    projectManager.SetBlockParameter(block.Id, "SigmaX", 0.0);
                    break;
                case "SimpleThreshold":
                    projectManager.SetBlockParameter(block.Id, "ThresholdValue", 128);
                    projectManager.SetBlockParameter(block.Id, "MaxValue", 255);
                    projectManager.SetBlockParameter(block.Id, "ThresholdType", SimpleThresholdType.Binary);
                    break;
                case "Morphology":
                    projectManager.SetBlockParameter(block.Id, "Operation", MorphologyOperationMode.Open);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);

                    break;
                case "FindContours":
                    projectManager.SetBlockParameter(block.Id, "RetrievalMode", "External");
                    projectManager.SetBlockParameter(block.Id, "ApproximationMode", "Simple");
                    break;
                case "ContourFilter":
                    projectManager.SetBlockParameter(block.Id, "MinimumArea", 100.0);
                    projectManager.SetBlockParameter(block.Id, "MaximumArea", 1_000_000.0);
                    projectManager.SetBlockParameter(block.Id, "MinimumWidth", 5);
                    projectManager.SetBlockParameter(block.Id, "MaximumWidth", 100_000);
                    projectManager.SetBlockParameter(block.Id, "MinimumHeight", 5);
                    projectManager.SetBlockParameter(block.Id, "MaximumHeight", 100_000);
                    projectManager.SetBlockParameter(block.Id, "MinimumPerimeter", 0.0);
                    projectManager.SetBlockParameter(block.Id, "MaximumPerimeter", 1_000_000.0);
                    break;

                case "ApproxPolyDP":
                    projectManager.SetBlockParameter(block.Id, "EpsilonMode", ApproxPolyDPEpsilonMode.PerimeterPercent);
                    projectManager.SetBlockParameter(block.Id, "Epsilon", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Closed", true);
                    break;

                case "Canny":
                    projectManager.SetBlockParameter(block.Id, "Threshold1", 50.0);
                    projectManager.SetBlockParameter(block.Id, "Threshold2", 150.0);
                    projectManager.SetBlockParameter(block.Id, "ApertureSize", 3);
                    projectManager.SetBlockParameter(block.Id, "L2Gradient", false);
                    break;

                case "AdaptiveThreshold":
                    projectManager.SetBlockParameter(block.Id, "Method", AdaptiveThresholdMethodMode.Gaussian);
                    projectManager.SetBlockParameter(block.Id, "ThresholdType", AdaptiveThresholdTypeMode.Binary);
                    projectManager.SetBlockParameter(block.Id, "MaxValue", 255.0);
                    projectManager.SetBlockParameter(block.Id, "BlockSize", 11);
                    projectManager.SetBlockParameter(block.Id, "C", 2.0);
                    break;

                case "EqualizeHist":
                    projectManager.SetBlockParameter(block.Id, "ConvertToGray", true);
                    break;

                case "CLAHE":
                    projectManager.SetBlockParameter(block.Id, "ClipLimit", 2.0);
                    projectManager.SetBlockParameter(block.Id, "TileGridWidth", 8);
                    projectManager.SetBlockParameter(block.Id, "TileGridHeight", 8);
                    break;

                case "BrightnessContrast":
                    projectManager.SetBlockParameter(block.Id, "Contrast", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Brightness", 0.0);
                    break;

                case "GammaCorrection":
                    projectManager.SetBlockParameter(block.Id, "Gamma", 1.0);
                    break;

                case "Normalize":
                    projectManager.SetBlockParameter(block.Id, "Mode", NormalizeMode.MinMax);
                    projectManager.SetBlockParameter(block.Id, "Alpha", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Beta", 255.0);
                    break;

                case "Invert":
                    break;

                case "Resize":
                    projectManager.SetBlockParameter(block.Id, "Mode", ResizeMode.ScalePercent);
                    projectManager.SetBlockParameter(block.Id, "Width", 640);
                    projectManager.SetBlockParameter(block.Id, "Height", 480);
                    projectManager.SetBlockParameter(block.Id, "ScalePercent", 100.0);
                    projectManager.SetBlockParameter(block.Id, "KeepAspectRatio", true);
                    projectManager.SetBlockParameter(block.Id, "Interpolation", ResizeInterpolation.Area);
                    break;

                case "Crop":
                    projectManager.SetBlockParameter(block.Id, "X", 0);
                    projectManager.SetBlockParameter(block.Id, "Y", 0);
                    projectManager.SetBlockParameter(block.Id, "Width", 640);
                    projectManager.SetBlockParameter(block.Id, "Height", 480);
                    projectManager.SetBlockParameter(block.Id, "ClampToImage", true);
                    break;

                case "Flip":
                    projectManager.SetBlockParameter(block.Id, "Mode", ImageFlipMode.Horizontal);
                    break;


                case "Rotate":
                    projectManager.SetBlockParameter(block.Id, "Angle", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    projectManager.SetBlockParameter(block.Id, "ExpandCanvas", true);
                    projectManager.SetBlockParameter(block.Id, "Interpolation", ResizeInterpolation.Linear);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Constant);
                    projectManager.SetBlockParameter(block.Id, "BorderValue", 0.0);
                    break;

                case "Translate":
                    projectManager.SetBlockParameter(block.Id, "ShiftX", 0);
                    projectManager.SetBlockParameter(block.Id, "ShiftY", 0);
                    projectManager.SetBlockParameter(block.Id, "ExpandCanvas", false);
                    projectManager.SetBlockParameter(block.Id, "Interpolation", ResizeInterpolation.Linear);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Constant);
                    projectManager.SetBlockParameter(block.Id, "BorderValue", 0.0);
                    break;

                case "AddBorder":
                    projectManager.SetBlockParameter(block.Id, "Top", 50);
                    projectManager.SetBlockParameter(block.Id, "Bottom", 50);
                    projectManager.SetBlockParameter(block.Id, "Left", 50);
                    projectManager.SetBlockParameter(block.Id, "Right", 50);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Constant);
                    projectManager.SetBlockParameter(block.Id, "BorderValue", 0.0);
                    break;

                case "MedianBlur":
                    projectManager.SetBlockParameter(block.Id, "KernelSize", 5);
                    break;

                case "GaussianBlur":
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 5);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 5);
                    projectManager.SetBlockParameter(block.Id, "SigmaX", 0.0);
                    projectManager.SetBlockParameter(block.Id, "SigmaY", 0.0);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;

                case "BilateralFilter":
                    projectManager.SetBlockParameter(block.Id, "Diameter", 9);
                    projectManager.SetBlockParameter(block.Id, "SigmaColor", 75.0);
                    projectManager.SetBlockParameter(block.Id, "SigmaSpace", 75.0);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;

                case "Sobel":
                    projectManager.SetBlockParameter(block.Id, "Direction", SobelDirection.Both);
                    projectManager.SetBlockParameter(block.Id, "KernelSize", 3);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Delta", 0.0);
                    projectManager.SetBlockParameter(block.Id, "ConvertToGray", true);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;

                case "Scharr":
                    projectManager.SetBlockParameter(block.Id, "Direction", SobelDirection.Both);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Delta", 0.0);
                    projectManager.SetBlockParameter(block.Id, "ConvertToGray", true);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;

                case "Laplacian":
                    projectManager.SetBlockParameter(block.Id, "KernelSize", 3);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Delta", 0.0);
                    projectManager.SetBlockParameter(block.Id, "ConvertToGray", true);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;


                case "PyrDown":
                    projectManager.SetBlockParameter(block.Id, "Levels", 1);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;


                case "PyrUp":
                    projectManager.SetBlockParameter(block.Id, "Levels", 1);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", RotateBorderMode.Reflect101);
                    break;


                case "PyrMeanShiftFiltering":
                    projectManager.SetBlockParameter(block.Id, "SpatialRadius", 10.0);
                    projectManager.SetBlockParameter(block.Id, "ColorRadius", 20.0);
                    projectManager.SetBlockParameter(block.Id, "MaxLevel", 1);
                    projectManager.SetBlockParameter(block.Id, "MaxIterations", 5);
                    projectManager.SetBlockParameter(block.Id, "Epsilon", 1.0);
                    break;

                case "DrawContours":
                    projectManager.SetBlockParameter(block.Id, "Blue", 0);
                    projectManager.SetBlockParameter(block.Id, "Green", 255);
                    projectManager.SetBlockParameter(block.Id, "Red", 0);
                    projectManager.SetBlockParameter(block.Id, "Thickness", 2);
                    break;


                case "ContourArea":
                    projectManager.SetBlockParameter(block.Id, "Oriented", false);
                    break;

                case "ArcLength":
                    projectManager.SetBlockParameter(block.Id, "Closed", true);
                    break;

                case "BoundingRect":
                    break;

                case "MinAreaRect":
                    break;

                case "AbsDiff":
                    break;

                case "CvtColor":
                    projectManager.SetBlockParameter(block.Id, "ConversionMode", ColorConversionMode.BgrToGray);
                    break;

                case "ExtractChannel":
                    projectManager.SetBlockParameter(block.Id, "ChannelIndex", 0);
                    break;


                case "ApplyColorMap":
                    projectManager.SetBlockParameter(block.Id, "ColorMap", ColorMapMode.Jet);
                    break;


                case "ConvertScaleAbs":
                    projectManager.SetBlockParameter(block.Id, "Alpha", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Beta", 0.0);
                    break;

                case "BoxFilter":
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Normalize", true);
                    projectManager.SetBlockParameter(block.Id, "BorderMode", BoxFilterBorderMode.Default);
                    break;


                case "InRange":
                    projectManager.SetBlockParameter(block.Id, "Lower0", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Lower1", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Lower2", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Lower3", 0.0);
                    projectManager.SetBlockParameter(block.Id, "Upper0", 255.0);
                    projectManager.SetBlockParameter(block.Id, "Upper1", 255.0);
                    projectManager.SetBlockParameter(block.Id, "Upper2", 255.0);
                    projectManager.SetBlockParameter(block.Id, "Upper3", 255.0);
                    break;

                case "Erode":
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    break;

                case "Dilate":
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    break;


                case "Thinning":
                    projectManager.SetBlockParameter(block.Id, "Algorithm", ThinningAlgorithmMode.ZhangSuen);
                    break;

                case "BitwiseNot":

                    break;

                case "AddScalar":
                    projectManager.SetBlockParameter(block.Id, "Value", 0.0);
                    break;

                case "SubtractScalar":
                    projectManager.SetBlockParameter(block.Id, "Value", 0.0);
                    break;

                case "MultiplyScalar":
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    break;

                case "DivideScalar":
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    break;

                case "Pow":
                    projectManager.SetBlockParameter(block.Id, "Power", 1.0);
                    break;

                case "Sqrt":

                    break;


                case "Abs":

                    break;

                case "BitwiseAnd":

                    break;

                case "BitwiseOr":
                    break;

                case "BitwiseXor":
                    break;

                case "AddWeighted":
                    break;

                case "Add":
                    break;

                case "Subtract":

                    break;

                case "Multiply":
                    break;
                case "Divide":
                    break;

                case "MorphologyOpen":
                    projectManager.SetBlockParameter(block.Id, "Operation", MorphologyOperationMode.Open);
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    break;

                case "MorphologyClose":
                    projectManager.SetBlockParameter(block.Id, "Operation", MorphologyOperationMode.Close);
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    break;

                case "MorphologyGradient":
                    projectManager.SetBlockParameter(block.Id, "Operation", MorphologyOperationMode.Gradient);
                    projectManager.SetBlockParameter(block.Id, "KernelWidth", 3);
                    projectManager.SetBlockParameter(block.Id, "KernelHeight", 3);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 1);
                    projectManager.SetBlockParameter(block.Id, "KernelShape", MorphologyKernelShape.Rectangle);
                    break;


                case "ChannelSelector":
                    projectManager.SetBlockParameter(block.Id, "FirstChannel", ChannelSelectionMode.Original);
                    projectManager.SetBlockParameter(block.Id, "SecondChannel", ChannelSelectionMode.Original);
                    break;

                case "SplitChannels":
                    break;
                case "MergeChannels":
                    break;

                case "SwapChannels":
                    projectManager.SetBlockParameter(block.Id, "SwapChannel0", false);
                    projectManager.SetBlockParameter(block.Id, "SwapChannel1", false);
                    projectManager.SetBlockParameter(block.Id, "SwapChannel2", false);
                    break;


                case "Demosaicing":
                    projectManager.SetBlockParameter(block.Id, "Pattern", DemosaicingPattern.RGGB);
                    projectManager.SetBlockParameter(block.Id, "Algorithm", DemosaicingAlgorithm.EdgeAware);
                    break;


                case "ImageSave":
                    projectManager.SetBlockParameter(block.Id, "FilePath", "result.png");
                    projectManager.SetBlockParameter(block.Id, "JpegQuality", 95);
                    break;


                case "MixChannels":
                    projectManager.SetBlockParameter(block.Id, "Channel0", 2);
                    projectManager.SetBlockParameter(block.Id, "Channel1", 1);
                    projectManager.SetBlockParameter(block.Id, "Channel2", 0);
                    break;



                case "Lut":
                    projectManager.SetBlockParameter(block.Id, "Mode", LutMode.Identity);
                    projectManager.SetBlockParameter(block.Id, "Gamma", 1.0);
                    projectManager.SetBlockParameter(block.Id, "Threshold", 128);
                    break;

                case "Transpose":

                    break;

                case "CopyMakeBorder":
                    projectManager.SetBlockParameter(block.Id, "Top", 10);
                    projectManager.SetBlockParameter(block.Id, "Bottom", 10);
                    projectManager.SetBlockParameter(block.Id, "Left", 10);
                    projectManager.SetBlockParameter(block.Id, "Right", 10);
                    projectManager.SetBlockParameter(block.Id, "BorderType", BorderTypeMode.Constant);
                    projectManager.SetBlockParameter(block.Id, "Value", 0);
                    break;

                case "GetRectSubPix":
                    projectManager.SetBlockParameter(block.Id, "CenterX", 100.0);
                    projectManager.SetBlockParameter(block.Id, "CenterY", 100.0);
                    projectManager.SetBlockParameter(block.Id, "Width", 100);
                    projectManager.SetBlockParameter(block.Id, "Height", 100);
                    break;

                case "Filter2D":
                    projectManager.SetBlockParameter(block.Id, "Kernel", Filter2DKernel.Identity);
                    break;

                case "SepFilter2D":
                    projectManager.SetBlockParameter(block.Id, "Kernel", SepFilterKernel.Identity);
                    break;

                case "FastNlMeansDenoisingColored":
                    projectManager.SetBlockParameter(block.Id, "H", 10f);
                    projectManager.SetBlockParameter(block.Id, "HColor", 10f);
                    projectManager.SetBlockParameter(block.Id, "TemplateWindowSize", 7);
                    projectManager.SetBlockParameter(block.Id, "SearchWindowSize", 21);
                    break;


                case "Magnitude":
                    projectManager.SetBlockParameter(block.Id, "Direction", MagnitudeDirection.Both);
                    projectManager.SetBlockParameter(block.Id, "KernelSize", 3);
                    projectManager.SetBlockParameter(block.Id, "UseBlur", true);
                    projectManager.SetBlockParameter(block.Id, "BlurSize", 3);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    break;


                case "Phase":
                    projectManager.SetBlockParameter(block.Id, "KernelSize", 3);
                    projectManager.SetBlockParameter(block.Id, "UseBlur", true);
                    projectManager.SetBlockParameter(block.Id, "BlurSize", 3);
                    projectManager.SetBlockParameter(block.Id, "Scale", 1.0);
                    projectManager.SetBlockParameter(block.Id, "AngleInDegrees", true);
                    break;


                case "CompareScalar":
                    projectManager.SetBlockParameter(block.Id, "Operation", CompareOperation.Greater);
                    projectManager.SetBlockParameter(block.Id, "Value", 128.0);
                    break;


                case "MinEnclosingCircle":

                    break;

                case "ConvexHull":
                    projectManager.SetBlockParameter(block.Id, "ContourIndex", -1);
                    projectManager.SetBlockParameter(block.Id, "MinArea", 0.0);
                    break;

                case "FitEllipse":
                    projectManager.SetBlockParameter(block.Id, "ContourIndex", -1);
                    projectManager.SetBlockParameter(block.Id, "MinPointCount", 5);
                    break;

                case "FindTransformECC":
                    projectManager.SetBlockParameter(block.Id, "MotionType", MotionTypes.Affine);
                    projectManager.SetBlockParameter(block.Id, "Iterations", 500);
                    projectManager.SetBlockParameter(block.Id, "Epsilon", 0.000001);
                    projectManager.SetBlockParameter(block.Id, "ShowResult", true);
                    break;


                case "AlignImage":
                    projectManager.SetBlockParameter(block.Id, "Interpolation", InterpolationFlags.Linear);
                    break;


            }
        }
        private void AddVisualBlock(ProjectBlock projectBlock)
        {
            PipelineBlockControl control = new PipelineBlockControl(projectBlock);
            BlockDescriptor? descriptor = FindDescriptor(projectBlock.BlockType);
            /*
             * Кнопка папки отображается только у блоков,
             * для которых в BlockDescriptor установлено:
             *
             * HasFileSelection = true
             */
            control.SetFileSelectionVisible(descriptor?.HasFileSelection == true);
            control.BlockSelected += PipelineBlockControl_BlockSelected;
            control.FileSelectionRequested += PipelineBlockControl_FileSelectionRequested;
            control.MoveUpRequested += PipelineBlockControl_MoveUpRequested;
            control.MoveDownRequested += PipelineBlockControl_MoveDownRequested;
            control.DeleteRequested += PipelineBlockControl_DeleteRequested;
            control.PreviewRequested += PipelineBlockControl_PreviewRequested;
            panelPipeline.Controls.Add(control);
        }
        private void PipelineBlockControl_FileSelectionRequested(object? sender, EventArgs e)
        {
            if (sender is not PipelineBlockControl control)
            {
                return;
            }
            try
            {
                SelectSourceImage(control.ProjectBlock);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Ошибка выбора изображения", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        private void SelectSourceImage(ProjectBlock projectBlock)
        {
            ArgumentNullException.ThrowIfNull(projectBlock);
            BlockDescriptor? descriptor = FindDescriptor(projectBlock.BlockType);
            if (descriptor == null)
            {
                throw new InvalidOperationException($"Не найдено описание блока «{projectBlock.BlockType}».");
            }
            if (!descriptor.HasFileSelection || descriptor.SourceSlot == SourceImageSlot.None)
            {
                throw new InvalidOperationException("Для данного блока выбор изображения не предусмотрен.");
            }
            using OpenFileDialog dialog = new OpenFileDialog
            {
                Title = descriptor.SourceSlot switch
                {
                    SourceImageSlot.Main => "Выберите основное изображение",
                    SourceImageSlot.Second => "Выберите второе изображение",
                    _ => "Выберите изображение"
                },
                Filter = "Изображения|*.png;*.jpg;*.jpeg;*.bmp;*.tif;*.tiff|PNG (*.png)|*.png|JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|Bitmap (*.bmp)|*.bmp|TIFF (*.tif;*.tiff)|*.tif;*.tiff|Все файлы (*.*)|*.*",
                CheckFileExists = true,
                CheckPathExists = true,
                Multiselect = false,
                RestoreDirectory = true
            };
            if (dialog.ShowDialog() != DialogResult.OK)
            {
                return;
            }
            string destinationBaseName = descriptor.SourceSlot switch
            {
                SourceImageSlot.Main => "main_source",
                SourceImageSlot.Second => "second_source",
                _ => projectBlock.Id.ToString("N")
            };
            string savedImagePath = projectManager.AddBlockImage(projectBlock.Id, dialog.FileName, destinationBaseName);
            PipelineBlockControl? sourceControl = panelPipeline.Controls.OfType<PipelineBlockControl>().FirstOrDefault(control => control.ProjectBlock.Id == projectBlock.Id);
            if (sourceControl != null)
            {
                SelectVisualControl(sourceControl);
            }
            MessageBox.Show($"Изображение сохранено:\n{savedImagePath}", "Изображение источника", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        private void PipelineBlockControl_MoveUpRequested(object? sender, EventArgs e)
        {
            if (sender is not PipelineBlockControl control) return;
            SelectVisualControl(control);
            MoveSelectedBlockUp();
        }
        private void PipelineBlockControl_MoveDownRequested(object? sender, EventArgs e)
        {
            if (sender is not PipelineBlockControl control) return;
            SelectVisualControl(control);
            MoveSelectedBlockDown();
        }
        private void PipelineBlockControl_DeleteRequested(object? sender, EventArgs e)
        {
            if (sender is not PipelineBlockControl control) return;
            SelectVisualControl(control);
            DialogResult result = MessageBox.Show($"Удалить блок «{control.ProjectBlock.Name}»?", "Удаление блока", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
            if (result != DialogResult.Yes) return;
            RemoveSelectedBlock();
        }
        private void PipelineBlockControl_PreviewRequested(object? sender, EventArgs e)
        {
            if (sender is not PipelineBlockControl control) return;
            SelectVisualControl(control);
            PreviewRequested?.Invoke(this, new ProjectBlockEventArgs(control.ProjectBlock));
        }
        private void ArrangePipelineBlocks()
        {
            List<PipelineBlockControl> controls = panelPipeline.Controls.OfType<PipelineBlockControl>().OrderBy(control => control.ProjectBlock.Order).ToList();
            int availableWidth = Math.Max(360, panelPipeline.ClientSize.Width - BlockLeft * 2 - SystemInformation.VerticalScrollBarWidth);
            int currentTop = BlockTop;
            for (int index = 0; index < controls.Count; index++)
            {
                PipelineBlockControl control = controls[index];
                control.Location = new System.Drawing. Point(BlockLeft, currentTop);
                control.Width = availableWidth;
                control.Height = BlockHeight;
                control.UpdateView();
                BlockDescriptor? descriptor = FindDescriptor(control.ProjectBlock.BlockType);
                bool isFixedBlock = descriptor?.CanMove == false;
                if (isFixedBlock)
                {
                    /*
                     * ImageSource и SecondImageSource
                     * вообще нельзя перемещать.
                     */
                    control.SetMoveButtonsEnabled(canMoveUp: false, canMoveDown: false);
                }
                else
                {
                    bool canMoveUp = false;
                    bool canMoveDown = index < controls.Count - 1;
                    if (index > 0)
                    {
                        PipelineBlockControl previousControl = controls[index - 1];
                        BlockDescriptor? previousDescriptor = FindDescriptor(previousControl.ProjectBlock.BlockType);
                        /*
                         * Обычный блок нельзя поднять выше
                         * ImageSource или SecondImageSource.
                         */
                        canMoveUp = previousDescriptor?.IsImageSource != true;
                    }
                    control.SetMoveButtonsEnabled(canMoveUp, canMoveDown);
                }
                currentTop += BlockHeight + BlockSpacing;
            }
            panelPipeline.AutoScrollMinSize = new System.Drawing. Size(0, currentTop + BlockTop);
        }
        private void RefreshVisualBlockOrder()
        {
            if (projectManager.CurrentProject == null) return;
            foreach (PipelineBlockControl control in panelPipeline.Controls.OfType<PipelineBlockControl>())
            {
                ProjectBlock? updatedBlock = projectManager.GetBlock(control.ProjectBlock.Id);
                if (updatedBlock != null) { control.ProjectBlock.Order = updatedBlock.Order; control.UpdateView(); }
            }
        }
        private void SelectVisualControl(PipelineBlockControl control)
        {
            if (selectedControl != null) selectedControl.SetSelected(false);
            selectedControl = control;
            selectedControl.SetSelected(true);
            if (propertyGridBlock != null)
            {
                propertyGridBlock.SelectedObject = BlockPropertiesFactory.Create(projectManager, selectedControl.ProjectBlock);
                propertyGridBlock.Refresh();
            }
            BlockSelected?.Invoke(this, new ProjectBlockEventArgs(selectedControl.ProjectBlock));
        }
        private void ClearPipelineControls()
        {
            foreach (PipelineBlockControl control in panelPipeline.Controls.OfType<PipelineBlockControl>().ToArray())
            {
                control.BlockSelected -= PipelineBlockControl_BlockSelected;
                control.FileSelectionRequested -= PipelineBlockControl_FileSelectionRequested;
                control.MoveUpRequested -= PipelineBlockControl_MoveUpRequested;
                control.MoveDownRequested -= PipelineBlockControl_MoveDownRequested;
                control.DeleteRequested -= PipelineBlockControl_DeleteRequested;
                control.PreviewRequested -= PipelineBlockControl_PreviewRequested;
                panelPipeline.Controls.Remove(control);
                control.Dispose();
            }
            selectedControl = null;
            if (propertyGridBlock != null) propertyGridBlock.SelectedObject = null;
        }
        private void ConfigureControls()
        {
            treeViewBlocks.HideSelection = false;
            treeViewBlocks.FullRowSelect = true;
            treeViewBlocks.ShowLines = true;
            treeViewBlocks.ShowPlusMinus = true;
            treeViewBlocks.ShowRootLines = true;
            treeViewBlocks.ShowNodeToolTips = true;
            panelPipeline.AutoScroll = true;
            panelPipeline.BackColor = Color.WhiteSmoke;
            panelPipeline.BorderStyle = BorderStyle.FixedSingle;
        }
        private void TreeViewBlocks_NodeMouseDoubleClick(object? sender, TreeNodeMouseClickEventArgs e)
        {
            try
            {
                if (e.Node.Tag is BlockCategory) { e.Node.Toggle(); return; }
                if (e.Node.Tag is not BlockDescriptor descriptor) return;
                treeViewBlocks.SelectedNode = e.Node;
                AddBlock(descriptor);
            }
            catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка добавления блока", MessageBoxButtons.OK, MessageBoxIcon.Error); }
        }
        private void PipelineBlockControl_BlockSelected(object? sender, EventArgs e)
        {
            if (sender is PipelineBlockControl control) SelectVisualControl(control);
        }
        private void ProjectManager_ProjectChanged(object? sender, EventArgs e) { }
        private void PanelPipeline_Resize(object? sender, EventArgs e) { ArrangePipelineBlocks(); }
        public void Dispose()
        {
            if (disposed) return;
            disposed = true;
            treeViewBlocks.NodeMouseDoubleClick -= TreeViewBlocks_NodeMouseDoubleClick;
            panelPipeline.Resize -= PanelPipeline_Resize;
            projectManager.ProjectChanged -= ProjectManager_ProjectChanged;
            ClearPipelineControls();
        }

        /*
           private void RegisterInputOutputBlocks()
           {
               RegisterBlock(blockType: "ImageSource", displayName: "Исходное изображение", description: "Загружает основное изображение проекта и записывает его в основной слот pipeline.", category: BlockCategory.InputOutput, inputType: BlockDataType.None, outputType: BlockDataType.Image, isImplemented: true, allowMultiple: false, canMove: false, hasFileSelection: true, sourceSlot: SourceImageSlot.Main);
               RegisterBlock(blockType: "SecondImageSource", displayName: "Второе изображение", description: "Загружает второе изображение для блоков, выполняющих операции над двумя изображениями.", category: BlockCategory.InputOutput, inputType: BlockDataType.None, outputType: BlockDataType.Image, isImplemented: true, allowMultiple: true, canMove: true, hasFileSelection: true, sourceSlot: SourceImageSlot.Second);
               RegisterBlock(blockType: "ImageSave", displayName: "Сохранение изображения", description: "Сохраняет текущее изображение pipeline в указанный файл.", category: BlockCategory.InputOutput, inputType: BlockDataType.Image, outputType: BlockDataType.Image, isImplemented: true);
           }
           private void RegisterColorChannelBlocks()
           {
               RegisterBlock("ColorChannel", "Цветовой канал", "Выделяет выбранный канал изображения.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("CvtColor", "Преобразование цвета", "Cv2.CvtColor: BGR, Gray, HSV, HLS, Lab и YCrCb.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Demosaicing", "Преобразование Bayer", "Cv2.Demosaicing: преобразует Bayer-изображение в цветное.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("ExtractChannel", "Извлечение канала", "Cv2.ExtractChannel: извлекает один канал.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("MergeChannels", "Объединение каналов", "Cv2.Merge: собирает несколько каналов в одно изображение.", BlockCategory.ColorChannels, BlockDataType.ImageCollection, BlockDataType.Image, isImplemented: true);
               RegisterBlock("MixChannels", "Перестановка каналов", "Cv2.MixChannels: копирует и переставляет каналы изображения.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("ApplyColorMap", "Псевдоцвет", "Cv2.ApplyColorMap: окрашивает полутоновое изображение.", BlockCategory.ColorChannels, BlockDataType.GrayImage, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Lut", "Таблица LUT", "Cv2.LUT: применяет таблицу преобразования значений.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
           }
           private void RegisterImageGeometryBlocks()
           {
               RegisterBlock("Resize", "Изменение размера", "Cv2.Resize: изменяет ширину и высоту изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Crop", "Обрезка ROI", "Извлекает прямоугольную область изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Flip", "Отражение", "Cv2.Flip: отражает изображение по выбранной оси.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Rotate", "Поворот на 90 градусов", "Cv2.Rotate: поворот на 90, 180 или 270 градусов.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Transpose", "Транспонирование", "Cv2.Transpose: меняет строки и столбцы местами.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("CopyMakeBorder", "Добавление рамки", "Cv2.CopyMakeBorder: добавляет рамку вокруг изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("GetRectSubPix", "Субпиксельный ROI", "Cv2.GetRectSubPix: извлекает область с субпиксельной точностью.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("PyrDown", "Уменьшение пирамиды", "Cv2.PyrDown: уменьшает изображение с фильтрацией.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("PyrUp", "Увеличение пирамиды", "Cv2.PyrUp: увеличивает изображение.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Normalize", "Нормализация", "Cv2.Normalize: приводит значения к заданному диапазону.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image);
               RegisterBlock("ConvertScaleAbs", "Масштаб яркости", "Cv2.ConvertScaleAbs: применяет коэффициент alpha и смещение beta.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Translate", "Сдвиг", "Сдвигает изображение по горизонтали и вертикали.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("AddBorder", "Добавление полей", "Добавляет поля вокруг изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
           }
           private void RegisterFilteringBlocks()
           {
               RegisterBlock("Blur", "Размытие", "Усредняющее, Gaussian, Median или Bilateral размытие.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("BoxFilter", "Блочный фильтр", "Cv2.BoxFilter: фильтрация прямоугольным ядром.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("GaussianBlur", "Гауссово размытие", "Cv2.GaussianBlur: сглаживание гауссовым ядром.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("MedianBlur", "Медианное размытие", "Cv2.MedianBlur: хорошо удаляет импульсный шум.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("BilateralFilter", "Двусторонний фильтр", "Cv2.BilateralFilter: подавляет шум, сохраняя границы.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Filter2D", "Произвольная свёртка", "Cv2.Filter2D: применяет пользовательское ядро.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("SepFilter2D", "Разделимая свёртка", "Cv2.SepFilter2D: применяет отдельные ядра X и Y.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("FastNlMeansDenoising", "Подавление шума NLM", "Cv2.FastNlMeansDenoising: подавление шума Non-Local Means.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("FastNlMeansDenoisingColored", "Цветное подавление шума NLM", "Подавляет шум в цветном изображении.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("PyrMeanShiftFiltering", "Mean Shift фильтрация", "Сглаживает цветовые области с сохранением границ.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
           }
           private void RegisterGradientBlocks()
           {
               RegisterBlock("Sobel", "Градиент Sobel", "Cv2.Sobel: вычисляет производную по X или Y.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("Scharr", "Градиент Scharr", "Cv2.Scharr: более точный градиент 3×3.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("Laplacian", "Лапласиан", "Cv2.Laplacian: выделяет резкие изменения яркости.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("Canny", "Границы Canny", "Cv2.Canny: формирует бинарную карту границ.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("Magnitude", "Модуль градиента", "Cv2.Magnitude: вычисляет длину вектора градиента.", BlockCategory.GradientsEdges, BlockDataType.TwoImages, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("Phase", "Направление градиента", "Вычисляет направление градиента через внутренние Sobel X и Sobel Y.", BlockCategory.GradientsEdges, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
           }
           private void RegisterThresholdBlocks()
           {
               RegisterBlock("SimpleThreshold", "Простой порог", "Обычный порог, Otsu или Triangle.", BlockCategory.Thresholding, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("AdaptiveThreshold", "Адаптивный порог", "Cv2.AdaptiveThreshold: локальный порог по участкам.", BlockCategory.Thresholding, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("InRange", "Диапазон значений", "Cv2.InRange: выделяет диапазон цвета или яркости.", BlockCategory.Thresholding, BlockDataType.Image, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("CompareScalar", "Сравнение с числом", "Сравнивает каждый пиксель с заданным значением.", BlockCategory.Thresholding, BlockDataType.Image, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("BitwiseNot", "Инверсия маски", "Cv2.BitwiseNot: инвертирует бинарную маску.", BlockCategory.Thresholding, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
           }
           private void RegisterMorphologyBlocks()
           {
               RegisterBlock("Morphology", "Морфология", "Erode, Dilate, Open, Close, Gradient, TopHat и BlackHat.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("Erode", "Эрозия", "Cv2.Erode: уменьшает белые области.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("Dilate", "Расширение", "Cv2.Dilate: расширяет белые области.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("MorphologyOpen", "Морфологическое открытие", "Удаляет небольшие белые объекты.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("MorphologyClose", "Морфологическое закрытие", "Заполняет разрывы и небольшие отверстия.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("MorphologyGradient", "Морфологическая граница", "Вычисляет разность расширения и эрозии.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("Thinning", "Скелетизация", "CvXImgProc.Thinning: формирует однопиксельный скелет.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
           }
           private void RegisterSingleImageMathBlocks()
           {
               RegisterBlock("AddScalar", "Прибавить значение", "Добавляет число или Scalar к каждому пикселю.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("SubtractScalar", "Вычесть значение", "Вычитает число или Scalar из каждого пикселя.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("MultiplyScalar", "Умножить на коэффициент", "Умножает значения пикселей на коэффициент.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("DivideScalar", "Разделить на коэффициент", "Делит значения пикселей на коэффициент.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Pow", "Возведение в степень", "Cv2.Pow: возводит значения пикселей в степень.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Sqrt", "Квадратный корень", "Cv2.Sqrt: вычисляет квадратный корень значений.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Abs", "Абсолютное значение", "Получает абсолютные значения элементов.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Invert", "Инверсия", "Инвертирует значения пикселей изображения.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
           }
           private void RegisterTwoImageMathBlocks()
           {
               RegisterBlock("Add", "Сложение изображений", "Cv2.Add: поэлементно складывает два изображения.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Subtract", "Вычитание изображений", "Cv2.Subtract: вычитает второе изображение из первого.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Multiply", "Умножение изображений", "Cv2.Multiply: поэлементно умножает изображения.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Divide", "Деление изображений", "Cv2.Divide: поэлементно делит изображения.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
               RegisterBlock("AbsDiff", "Абсолютная разница", "Cv2.Absdiff: вычисляет абсолютную разницу изображений.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
               RegisterBlock("BitwiseAnd", "Логическое AND", "Пересечение двух бинарных масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("BitwiseOr", "Логическое OR", "Объединение двух бинарных масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("BitwiseXor", "Логическое XOR", "Исключающее OR двух масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
               RegisterBlock("AddWeighted", "Смешивание изображений", "Cv2.AddWeighted: смешивает изображения с весами.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
           }
           private void RegisterContourBlocks()
           {
               RegisterBlock("FindContours", "Поиск контуров", "Находит контуры и их иерархию.", BlockCategory.Contours, BlockDataType.BinaryMask, BlockDataType.Contours, isImplemented: true);
               RegisterBlock("DrawContours", "Рисование контуров", "Рисует найденные контуры на изображении.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Image, isImplemented: true);
               RegisterBlock("ContourFilter", "Фильтр контуров", "Отбирает контуры по площади, размерам и периметру.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);
               RegisterBlock("ApproxPolyDP", "Аппроксимация контура", "Аппроксимирует контур многоугольником.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);


           }
           private void RegisterContourGeometryBlocks()
           {
               RegisterBlock("ContourArea", "Площадь контура", "Cv2.ContourArea: вычисляет площадь контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number, isImplemented: true);
               RegisterBlock("ArcLength", "Периметр контура", "Cv2.ArcLength: вычисляет длину контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number, isImplemented: true);
               RegisterBlock("BoundingRect", "Ограничивающий прямоугольник", "Cv2.BoundingRect: находит обычный прямоугольник.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Rectangle, isImplemented: true);
               RegisterBlock("MinAreaRect", "Повёрнутый прямоугольник", "Cv2.MinAreaRect: находит минимальный повёрнутый прямоугольник.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.RotatedRectangle, isImplemented: true);
               RegisterBlock("MinEnclosingCircle", "Описанная окружность", "Находит минимальную окружность вокруг контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Points, isImplemented: true);
               RegisterBlock("ConvexHull", "Выпуклая оболочка", "Cv2.ConvexHull: строит выпуклую оболочку.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);
               RegisterBlock("FitEllipse", "Аппроксимация эллипсом", "Cv2.FitEllipse: аппроксимирует контур эллипсом.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.RotatedRectangle, isImplemented: true);
               RegisterBlock("Moments", "Моменты контура", "Cv2.Moments: вычисляет геометрические моменты.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Scalar);
               RegisterBlock("MatchShapes", "Сравнение формы", "Cv2.MatchShapes: сравнивает формы двух контуров.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number);
           }
           private void RegisterHistogramBlocks()
           {
               RegisterBlock("EqualizeHist", "Выравнивание гистограммы", "Повышает контраст изображения путем выравнивания гистограммы.", BlockCategory.HistogramContrast, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("CLAHE", "Локальное выравнивание CLAHE", "Локально повышает контраст изображения с ограничением усиления шума.", BlockCategory.HistogramContrast, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
               RegisterBlock("BrightnessContrast", "Яркость и контраст", "Изменяет яркость и контраст изображения.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("GammaCorrection", "Гамма-коррекция", "Нелинейно изменяет яркость изображения с помощью гамма-кривой.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
               RegisterBlock("Normalize", "Нормализация", "Нормализует значения пикселей изображения.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);

           }


           private void RegisterStatisticsBlocks() { }
           private void RegisterSegmentationBlocks() { }
           private void RegisterRegistrationBlocks() { }
           private void RegisterGeometricTransformBlocks() 
           {
               RegisterBlock("FindTransformECC", "Выравнивание ECC", "Вычисляет смещение, поворот и масштаб двух изображений.", BlockCategory.Registration, BlockDataType.TwoImages, BlockDataType.TransformMatrix, isImplemented: true);
               RegisterBlock("AlignImage", "Выравнивание изображения", "Применяет найденную матрицу преобразования ECC: смещение, поворот и масштабирование изображения. Пустые области заполняются белым цветом.", BlockCategory.Registration, BlockDataType.Image, BlockDataType.Image, isImplemented: true);


           }
           private void RegisterMultiImageBlocks()
           {
               RegisterBlock("ChannelSelector", "Выбор каналов", "Извлекает выбранные цветовые каналы из двух изображений и передает результат в два выходных слота.", BlockCategory.MultiImage, BlockDataType.TwoImages, BlockDataType.TwoImages, isImplemented: true);
               RegisterBlock("SplitChannels", "Разделение каналов", "Cv2.Split: разделяет цветное изображение на отдельные каналы B, G, R.", BlockCategory.MultiImage, BlockDataType.Image, BlockDataType.ChannelCollection, isImplemented: true);
               RegisterBlock("SwapChannels", "Обмен каналами изображений", "Обменивает выбранные каналы между основным и вторым изображениями.", BlockCategory.MultiImage, BlockDataType.TwoImages, BlockDataType.TwoImages, isImplemented: true);
           }
          


           */


        private void RegisterInputOutputBlocks()
        {
            RegisterBlock(blockType: "ImageSource", displayName: "Исходное изображение", description: "Cv2.ImRead: загружает изображение из файла проекта.", category: BlockCategory.InputOutput, inputType: BlockDataType.None, outputType: BlockDataType.Image, isImplemented: true, allowMultiple: false, canMove: false, hasFileSelection: true, sourceSlot: SourceImageSlot.Main);
            RegisterBlock(blockType: "SecondImageSource", displayName: "Второе изображение", description: "Cv2.ImRead: загружает второе изображение для операций с двумя изображениями.", category: BlockCategory.InputOutput, inputType: BlockDataType.None, outputType: BlockDataType.Image, isImplemented: true, allowMultiple: true, canMove: true, hasFileSelection: true, sourceSlot: SourceImageSlot.Second);
            RegisterBlock(blockType: "ImageSave", displayName: "Сохранение изображения", description: "Cv2.ImWrite: сохраняет изображение pipeline в файл.", category: BlockCategory.InputOutput, inputType: BlockDataType.Image, outputType: BlockDataType.Image, isImplemented: true);
        }
        private void RegisterColorChannelBlocks()
        {
            RegisterBlock("ColorChannel", "Цветовой канал", "Cv2.ExtractChannel: извлекает выбранный канал изображения.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("CvtColor", "Преобразование цвета", "Cv2.CvtColor: преобразует цветовые пространства BGR, Gray, HSV, HLS, Lab и YCrCb.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Demosaicing", "Преобразование Bayer", "Cv2.Demosaicing: преобразует Bayer-изображение камеры в цветное.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("ExtractChannel", "Извлечение канала", "Cv2.ExtractChannel: извлекает один канал изображения.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("MergeChannels", "Объединение каналов", "Cv2.Merge: объединяет несколько каналов в одно изображение.", BlockCategory.ColorChannels, BlockDataType.ImageCollection, BlockDataType.Image, isImplemented: true);
            RegisterBlock("MixChannels", "Перестановка каналов", "Cv2.MixChannels: копирует и переставляет каналы изображения.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("ApplyColorMap", "Псевдоцвет", "Cv2.ApplyColorMap: преобразует изображение яркости в цветовую карту.", BlockCategory.ColorChannels, BlockDataType.GrayImage, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Lut", "Таблица LUT", "Cv2.LUT: выполняет преобразование значений пикселей по таблице.", BlockCategory.ColorChannels, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterImageGeometryBlocks()
        {
            RegisterBlock("Resize", "Изменение размера", "Cv2.Resize: изменяет ширину и высоту изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Crop", "Обрезка ROI", "ROI: извлекает прямоугольную область изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Flip", "Отражение", "Cv2.Flip: отражает изображение по выбранной оси.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Rotate", "Поворот на 90 градусов", "Cv2.Rotate: выполняет поворот изображения на 90, 180 или 270 градусов.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Transpose", "Транспонирование", "Cv2.Transpose: меняет строки и столбцы изображения местами.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("CopyMakeBorder", "Добавление рамки", "Cv2.CopyMakeBorder: добавляет рамку вокруг изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("GetRectSubPix", "Субпиксельный ROI", "Cv2.GetRectSubPix: извлекает область с субпиксельной точностью.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("PyrDown", "Уменьшение пирамиды", "Cv2.PyrDown: уменьшает изображение с предварительным сглаживанием.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("PyrUp", "Увеличение пирамиды", "Cv2.PyrUp: увеличивает изображение с восстановлением масштаба.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("ConvertScaleAbs", "Масштаб яркости", "Cv2.ConvertScaleAbs: применяет коэффициенты alpha и beta к изображению.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Translate", "Сдвиг", "Cv2.WarpAffine: выполняет сдвиг изображения по X и Y.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("AddBorder", "Добавление полей", "Cv2.CopyMakeBorder: добавляет поля вокруг изображения.", BlockCategory.ImageGeometry, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterFilteringBlocks()
        {
            RegisterBlock("Blur", "Размытие", "Cv2.Blur / GaussianBlur / MedianBlur / BilateralFilter: выполняет сглаживание изображения.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("BoxFilter", "Блочный фильтр", "Cv2.BoxFilter: выполняет фильтрацию прямоугольным ядром.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("GaussianBlur", "Гауссово размытие", "Cv2.GaussianBlur: выполняет сглаживание гауссовым фильтром.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("MedianBlur", "Медианное размытие", "Cv2.MedianBlur: удаляет импульсный шум с сохранением границ.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("BilateralFilter", "Двусторонний фильтр", "Cv2.BilateralFilter: подавляет шум с сохранением контуров.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Filter2D", "Произвольная свёртка", "Cv2.Filter2D: применяет пользовательское ядро свёртки.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("SepFilter2D", "Разделимая свёртка", "Cv2.SepFilter2D: выполняет свёртку раздельными ядрами X и Y.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("FastNlMeansDenoising", "Подавление шума NLM", "Cv2.FastNlMeansDenoising: удаляет шум методом Non-Local Means.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("FastNlMeansDenoisingColored", "Цветное подавление шума NLM", "Cv2.FastNlMeansDenoisingColored: удаляет шум в цветном изображении.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("PyrMeanShiftFiltering", "Mean Shift фильтрация", "Cv2.PyrMeanShiftFiltering: сглаживает цветовые области с сохранением границ.", BlockCategory.Filtering, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterGradientBlocks()
        {
            RegisterBlock("Sobel", "Градиент Sobel", "Cv2.Sobel: вычисляет производную изображения по X или Y.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("Scharr", "Градиент Scharr", "Cv2.Scharr: вычисляет более точный градиент 3×3 по X или Y.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("Laplacian", "Лапласиан", "Cv2.Laplacian: выделяет резкие изменения яркости изображения.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("Canny", "Границы Canny", "Cv2.Canny: обнаруживает границы объектов и создаёт бинарную карту.", BlockCategory.GradientsEdges, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("Magnitude", "Модуль градиента", "Cv2.Magnitude: вычисляет длину вектора градиента из X и Y компонентов.", BlockCategory.GradientsEdges, BlockDataType.TwoImages, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("Phase", "Направление градиента", "Cv2.Phase: вычисляет направление градиента по компонентам X и Y.", BlockCategory.GradientsEdges, BlockDataType.Image, BlockDataType.GrayImage, isImplemented: true);
        }
        private void RegisterThresholdBlocks()
        {
            RegisterBlock("SimpleThreshold", "Простой порог", "Cv2.Threshold: выполняет бинаризацию изображения с обычным, Otsu или Triangle порогом.", BlockCategory.Thresholding, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("AdaptiveThreshold", "Адаптивный порог", "Cv2.AdaptiveThreshold: выполняет локальную бинаризацию по участкам изображения.", BlockCategory.Thresholding, BlockDataType.GrayImage, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("InRange", "Диапазон значений", "Cv2.InRange: выделяет пиксели заданного диапазона цвета или яркости.", BlockCategory.Thresholding, BlockDataType.Image, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("CompareScalar", "Сравнение с числом", "Cv2.Compare: сравнивает каждый пиксель изображения с заданным значением.", BlockCategory.Thresholding, BlockDataType.Image, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("BitwiseNot", "Инверсия маски", "Cv2.BitwiseNot: инвертирует значения бинарной маски.", BlockCategory.Thresholding, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
        }

        private void RegisterMorphologyBlocks()
        {
            RegisterBlock("Morphology", "Морфология", "Cv2.MorphologyEx: выполняет морфологические операции Open, Close, Gradient, TopHat и BlackHat.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("Erode", "Эрозия", "Cv2.Erode: уменьшает белые области бинарной маски.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("Dilate", "Расширение", "Cv2.Dilate: расширяет белые области бинарной маски.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("MorphologyOpen", "Морфологическое открытие", "Cv2.MorphologyEx: удаляет мелкие объекты и шум бинарной маски.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("MorphologyClose", "Морфологическое закрытие", "Cv2.MorphologyEx: заполняет разрывы и небольшие отверстия.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("MorphologyGradient", "Морфологическая граница", "Cv2.MorphologyEx: вычисляет границу объекта разностью Dilate и Erode.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("Thinning", "Скелетизация", "CvXImgProc.Thinning: формирует однопиксельный скелет объекта.", BlockCategory.Morphology, BlockDataType.BinaryMask, BlockDataType.BinaryMask, isImplemented: true);
        }
        private void RegisterSingleImageMathBlocks()
        {
            RegisterBlock("AddScalar", "Прибавить значение", "Cv2.Add: добавляет заданное значение к пикселям изображения.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("SubtractScalar", "Вычесть значение", "Cv2.Subtract: вычитает заданное значение из пикселей изображения.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("MultiplyScalar", "Умножить на коэффициент", "Cv2.Multiply: умножает значения пикселей на коэффициент.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("DivideScalar", "Разделить на коэффициент", "Cv2.Divide: делит значения пикселей на коэффициент.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Pow", "Возведение в степень", "Cv2.Pow: возводит значения пикселей изображения в степень.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Sqrt", "Квадратный корень", "Cv2.Sqrt: вычисляет квадратный корень значений пикселей.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Abs", "Абсолютное значение", "Cv2.Abs: вычисляет абсолютные значения элементов изображения.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Invert", "Инверсия", "Cv2.BitwiseNot: инвертирует значения пикселей изображения.", BlockCategory.SingleImageMath, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterTwoImageMathBlocks()
        {
            RegisterBlock("Add", "Сложение изображений", "Cv2.Add: выполняет поэлементное сложение двух изображений.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Subtract", "Вычитание изображений", "Cv2.Subtract: вычитает второе изображение из первого.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Multiply", "Умножение изображений", "Cv2.Multiply: выполняет поэлементное умножение изображений.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Divide", "Деление изображений", "Cv2.Divide: выполняет поэлементное деление изображений.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
            RegisterBlock("AbsDiff", "Абсолютная разница", "Cv2.AbsDiff: вычисляет абсолютную разницу двух изображений.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
            RegisterBlock("BitwiseAnd", "Логическое AND", "Cv2.BitwiseAnd: выполняет пересечение двух бинарных масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("BitwiseOr", "Логическое OR", "Cv2.BitwiseOr: выполняет объединение двух бинарных масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("BitwiseXor", "Логическое XOR", "Cv2.BitwiseXor: выполняет исключающее объединение масок.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.BinaryMask, isImplemented: true);
            RegisterBlock("AddWeighted", "Смешивание изображений", "Cv2.AddWeighted: смешивает два изображения с заданными весами.", BlockCategory.TwoImageMath, BlockDataType.TwoImages, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterContourBlocks()
        {
            RegisterBlock("FindContours", "Поиск контуров", "Cv2.FindContours: находит контуры объектов и их иерархию.", BlockCategory.Contours, BlockDataType.BinaryMask, BlockDataType.Contours, isImplemented: true);
            RegisterBlock("DrawContours", "Рисование контуров", "Cv2.DrawContours: рисует найденные контуры на изображении.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Image, isImplemented: true);
            RegisterBlock("ContourFilter", "Фильтр контуров", "Cv2.ContourArea / BoundingRect: отбирает контуры по геометрическим параметрам.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);
            RegisterBlock("ApproxPolyDP", "Аппроксимация контура", "Cv2.ApproxPolyDP: упрощает контур до многоугольника.", BlockCategory.Contours, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);
        }
        private void RegisterContourGeometryBlocks()
        {
            RegisterBlock("ContourArea", "Площадь контура", "Cv2.ContourArea: вычисляет площадь найденного контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number, isImplemented: true);
            RegisterBlock("ArcLength", "Периметр контура", "Cv2.ArcLength: вычисляет длину контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number, isImplemented: true);
            RegisterBlock("BoundingRect", "Ограничивающий прямоугольник", "Cv2.BoundingRect: вычисляет прямоугольник вокруг контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Rectangle, isImplemented: true);
            RegisterBlock("MinAreaRect", "Повёрнутый прямоугольник", "Cv2.MinAreaRect: вычисляет минимальный повёрнутый прямоугольник контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.RotatedRectangle, isImplemented: true);
            RegisterBlock("MinEnclosingCircle", "Описанная окружность", "Cv2.MinEnclosingCircle: вычисляет минимальную окружность вокруг контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Points, isImplemented: true);
            RegisterBlock("ConvexHull", "Выпуклая оболочка", "Cv2.ConvexHull: строит выпуклую оболочку контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Contours, isImplemented: true);
            RegisterBlock("FitEllipse", "Аппроксимация эллипсом", "Cv2.FitEllipse: вычисляет эллипс, приближённый к контуру.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.RotatedRectangle, isImplemented: true);
            RegisterBlock("Moments", "Моменты контура", "Cv2.Moments: вычисляет геометрические моменты контура.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Scalar);
            RegisterBlock("MatchShapes", "Сравнение формы", "Cv2.MatchShapes: сравнивает формы двух контуров.", BlockCategory.ContourGeometry, BlockDataType.Contours, BlockDataType.Number);
        }
        private void RegisterHistogramBlocks()
        {
            RegisterBlock("EqualizeHist", "Выравнивание гистограммы", "Cv2.EqualizeHist: повышает контраст изображения методом выравнивания гистограммы.", BlockCategory.HistogramContrast, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("CLAHE", "Локальное выравнивание CLAHE", "Cv2.CLAHE: выполняет локальное выравнивание контраста с ограничением усиления.", BlockCategory.HistogramContrast, BlockDataType.GrayImage, BlockDataType.GrayImage, isImplemented: true);
            RegisterBlock("BrightnessContrast", "Яркость и контраст", "Cv2.ConvertTo: изменяет яркость и контраст изображения через alpha и beta.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("GammaCorrection", "Гамма-коррекция", "Cv2.LUT: выполняет нелинейную коррекцию яркости через гамма-таблицу.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
            RegisterBlock("Normalize", "Нормализация", "Cv2.Normalize: приводит диапазон значений пикселей к заданному диапазону.", BlockCategory.HistogramContrast, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }
        private void RegisterGeometricTransformBlocks()
        {
            RegisterBlock("FindTransformECC", "Выравнивание ECC", "Cv2.FindTransformECC: вычисляет матрицу смещения, поворота и масштаба между изображениями.", BlockCategory.Registration, BlockDataType.TwoImages, BlockDataType.TransformMatrix, isImplemented: true);
            RegisterBlock("AlignImage", "Выравнивание изображения", "Cv2.WarpAffine: применяет матрицу преобразования и выравнивает изображение.", BlockCategory.Registration, BlockDataType.Image, BlockDataType.Image, isImplemented: true);
        }

        private void RegisterMultiImageBlocks()
        {
            RegisterBlock("ChannelSelector", "Выбор каналов", "Cv2.Split / Cv2.Merge: извлекает и формирует выбранные каналы двух изображений.", BlockCategory.MultiImage, BlockDataType.TwoImages, BlockDataType.TwoImages, isImplemented: true);
            RegisterBlock("SplitChannels", "Разделение каналов", "Cv2.Split: разделяет цветное изображение на отдельные каналы B, G, R.", BlockCategory.MultiImage, BlockDataType.Image, BlockDataType.ChannelCollection, isImplemented: true);
            RegisterBlock("SwapChannels", "Обмен каналами изображений", "Cv2.MixChannels: выполняет обмен каналами между изображениями.", BlockCategory.MultiImage, BlockDataType.TwoImages, BlockDataType.TwoImages, isImplemented: true);
        }

        private void RegisterStatisticsBlocks() { }
        private void RegisterSegmentationBlocks() { }
        private void RegisterDiagnosticBlocks() { }
        private void RegisterRegistrationBlocks() { }




    }
    public sealed class ProjectBlockEventArgs : EventArgs
    {
        public ProjectBlock Block { get; }
        public ProjectBlockEventArgs(ProjectBlock block) { Block = block; }
    }
}