using HyperCube.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Identity;
using Microsoft.JSInterop;
using Pullenti.Unitext;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Console = HyperCube.Utils.AdvConsole;

namespace HyperCube.Pages
{
    public partial class Desktop : ComponentBase
    {
        //[Parameter]
        //public int DocID { get; set; }

        [Inject]
        NavigationManager NavigationManager { get; set; }
        [Inject]
        AuthenticationStateProvider AuthenticationStateProvider { get; set; }
        [Inject]
        UserManager<IdentityUser> UserManager { get; set; }
        [Inject]
        RoleManager<IdentityRole> RoleManager { get; set; }
        [Inject]
        AppData AppData { get; set; }
        [Inject]
        public IJSRuntime JsRuntime { get; set; }

        const string STORAGE_FOLDER_NAME = "articles_storage";
        const long MAX_FILE_SIZE = 5120000; //bytes

        const string ACTIVE_BUTTON_CLASS = "btn_white tab-button active";
        const string ACTIVE_TAB_CLASS = "second-block__form visible";
        const string BUTTON_CLASS = "btn_white tab-button";
        const string TAB_CLASS = "second-block__form";

        string _uploadButtonClass = ACTIVE_BUTTON_CLASS;
        string _uploadTabClass = ACTIVE_TAB_CLASS;
        string _verifyButtonClass = BUTTON_CLASS;
        string _verifyTabClass = TAB_CLASS;
        string _otherButtonClass = BUTTON_CLASS;
        string _otherTabClass = TAB_CLASS;

        //int _counter = 1;

        //string _event = "";
        string _status;
        string _articleDropdownOption = "";
        //string _storageFolderPath;
        MemoryStream _memoryStream;
        ModalInfo _modalInfo { get; set; }
        ModalLoading _modalLoading { get; set; }

        ArticleModel _articleClone;
        ArticleModel _article;
        Dictionary<int, ArticleModel> _articles = new();

        ReportModel _report = new();
        UnitextDocument _document;
        AccountModel _currentAccount;
        VerificationPoint _verificationPoint = new();

        bool loadButtonDisable { get; set; }
        bool verifyButtonDisable { get; set; }

        bool rejectReasonDisable { get; set; } = true;

        protected override async Task OnInitializedAsync()
        {
            _currentAccount = (AppData.CurrentAccount != null) ? AppData.CurrentAccount : await GetCurrentAcc();
            _currentAccount.LoadRoles();
            Console.WriteLine($"Desktop OnInitializedAsync, CurrentAccount: {_currentAccount.Name}");

            ///tmp
            await LoadArticles();

            _article = AppData.CurrentArticle ?? (new());
            _articleClone = AppData.CurrentArticleClone ?? (new());
        }        

        protected override void OnAfterRender(bool firstRender)
        {
            if (_verificationPoint.RulesViolation || _verificationPoint.NonExpert || _verificationPoint.AdditionalVerificationRequired || _verificationPoint.Rejected)
            {
                if (rejectReasonDisable)
                {
                    rejectReasonDisable = false;
                    StateHasChanged();
                }
            }
            if (!_verificationPoint.RulesViolation && !_verificationPoint.NonExpert && !_verificationPoint.AdditionalVerificationRequired && !_verificationPoint.Rejected)
            {
                if (!rejectReasonDisable)
                {
                    rejectReasonDisable = true;
                    StateHasChanged();
                }                
            }
        }

        void SwitchDesktopTab(int tabIndex)
        {
            switch(tabIndex)
            {
                case 0:
                    _uploadButtonClass = ACTIVE_BUTTON_CLASS;
                    _uploadTabClass = ACTIVE_TAB_CLASS;
                    _verifyButtonClass = BUTTON_CLASS;
                    _verifyTabClass = TAB_CLASS;
                    _otherButtonClass = BUTTON_CLASS;
                    _otherTabClass = TAB_CLASS;
                    break;
                case 1:
                    _uploadButtonClass = BUTTON_CLASS;
                    _uploadTabClass = TAB_CLASS;
                    _verifyButtonClass = ACTIVE_BUTTON_CLASS;
                    _verifyTabClass = ACTIVE_TAB_CLASS;
                    _otherButtonClass = BUTTON_CLASS;
                    _otherTabClass = TAB_CLASS;
                    break;
                case 2:
                    _uploadButtonClass = BUTTON_CLASS;
                    _uploadTabClass = TAB_CLASS;
                    _verifyButtonClass = BUTTON_CLASS;
                    _verifyTabClass = TAB_CLASS;
                    _otherButtonClass = ACTIVE_BUTTON_CLASS;
                    _otherTabClass = ACTIVE_TAB_CLASS;
                    break;
            }
        }

        async Task WidgetMenuClick(string menuname, string elementid)
        {            
            await JsRuntime.InvokeVoidAsync("WidgetMenuClick", menuname, elementid);
            //_counter = 1;
        }

        async Task HandleUpload(InputFileChangeEventArgs e)
        {
            _modalLoading.Open();

            IBrowserFile file = e.File;
            if (file != null)
            {
                Stream stream = file.OpenReadStream(MAX_FILE_SIZE);
                _memoryStream = new();
                await stream.CopyToAsync(_memoryStream);
                _status = $"Finished loading {_memoryStream.Length} bytes from {file.Name}";
                Console.WriteLine(_status);

                /// calculating hash
                string hash = await CalculateHashSum(_memoryStream);
                Console.WriteLine($"Hash: {hash}");

                /// checking hash
                MySQLConnector dbCon = MySQLConnector.Instance();
                string stringSQL;
                stringSQL = $"SELECT COUNT(*) FROM articles WHERE file_hash='{hash}'";
                int count = await dbCon.SQLSelectCount(stringSQL);                

                if (count < 1)
                {
                    _report = new();
                    _report.FileName = file.Name;
                    _report.FileSize = _memoryStream.Length.ToString();

                    _memoryStream.Position = 0;
                    byte[] content = _memoryStream.ToArray();

                    _document = UnitextService.CreateDocument(null, content, null);
                    if (_document.ErrorMessage != null)
                    {
                        // скорее всего, этот формат не поддерживается на данный момент
                        Console.WriteLine($"error, sorry: {_document.ErrorMessage}");

                        _memoryStream.Close();
                        stream.Close();
                        _modalLoading.Close();
                        _modalInfo.Open("Не удается прочитать документ, формат не поддерживается или файл поврежден.");

                        return;
                    }

                    // восстанавливаем имя исходного файла, извлечённого из ресурсов
                    _document.SourceFileName = file.Name;
                    for (int i = file.Name.Length - 7; i > 0; i--)
                    {
                        if (file.Name[i] == '.')
                        {
                            _document.SourceFileName = file.Name.Substring(i + 1);
                            break;
                        }
                    }

                    //// записываем результат в XML
                    //using (FileStream fs = new(doc.SourceFileName + ".xml", FileMode.Create, FileAccess.Write))
                    //{
                    //    XmlWriterSettings xmlParams = new();
                    //    xmlParams.Encoding = Encoding.UTF8;
                    //    xmlParams.Indent = true;
                    //    xmlParams.IndentChars = "  ";
                    //    using (XmlWriter xml = XmlWriter.Create(fs, xmlParams))
                    //    {
                    //        xml.WriteStartDocument();
                    //        doc.GetXml(xml);
                    //        xml.WriteEndDocument();
                    //    }
                    //}
                    //Console.WriteLine("XML write done");

                    // получаем плоский текст
                    string plainText = _document.GetPlaintextString(null);
                    if (plainText == null)
                        plainText = "Текст не выделен";

                    /// todo обработка, если статус != новый
                    _articleClone = DocParse.GetBaseProperties(plainText);
                    _articleClone.Filename = file.Name;
                    _articleClone.HashSum = hash;
                    _articleClone.CharCount = plainText.Length;
                    _article = (ArticleModel)_articleClone.Clone();

                    ///tmp
                    AppData.CurrentArticle = _article;

                    Console.WriteLine($"Initializing SDK Pullenti ver {Pullenti.Sdk.Version} ({Pullenti.Sdk.VersionDate})... ");
                    Pullenti.Sdk.InitializeAll();
                    //Console.WriteLine($"OK (by ... ms), version {Pullenti.Ner.ProcessorService.Version}");

                    List<string> npt_tokens = new();

                    // запускаем обработку на пустом процессоре (без анализаторов NER)
                    Pullenti.Ner.AnalysisResult are = Pullenti.Ner.ProcessorService.EmptyProcessor.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
                    //System.Console.Write("Noun groups: ");
                    // перебираем токены
                    for (Pullenti.Ner.Token t = are.FirstToken; t != null; t = t.Next)
                    {
                        // выделяем именную группу с текущего токена
                        Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.No, 0, null);
                        // не получилось
                        if (npt == null)
                            continue;
                        // получилось, выводим в нормализованном виде
                        //System.Console.Write($"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ");
                        //_report.NounGroups += $"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ";
                        _article.NounGroups += $"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ";
                        npt_tokens.Add(npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false));

                        // указатель на последний токен именной группы
                        t = npt.EndToken;
                    }
                    using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateProcessor())
                    {
                        // анализируем текст
                        Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
                        // результирующие сущности
                        //Console.WriteLine("\r\n==========================================\r\nEntities: ");
                        foreach (Pullenti.Ner.Referent en in ar.Entities)
                        {
                            //Console.WriteLine($"{en.TypeName}: {en}");
                            //_report.Entities += $"{en.TypeName}: {en}\r\n";
                            _article.Entities += $"{en.TypeName}: {en}\r\n";
                            foreach (Pullenti.Ner.Slot s in en.Slots)
                            {
                                //Console.WriteLine($"   {s.TypeName}: {s.Value}");
                                //_report.Entities += $"   {s.TypeName}: {s.Value}<br>";
                                _article.Entities += $"   {s.TypeName}: {s.Value}<br>";
                            }
                        }
                        // пример выделения именных групп
                        //Console.WriteLine("\r\n==========================================\r\nNoun groups: ");
                        for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
                        {
                            // токены с сущностями игнорируем
                            if (t.GetReferent() != null)
                                continue;
                            // пробуем создать именную группу
                            Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.AdjectiveCanBeLast, 0, null);
                            // не получилось
                            if (npt == null)
                                continue;
                            //Console.WriteLine(npt.ToString());
                            //_report.EntitiesNounGroups += $"{npt}<br>";
                            _article.Morph += $"{npt}<br>";
                            // указатель перемещаем на последний токен группы
                            t = npt.EndToken;
                        }
                    }
                    using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateSpecificProcessor(Pullenti.Ner.Keyword.KeywordAnalyzer.ANALYZER_NAME))
                    {
                        Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
                        //Console.WriteLine("\r\n==========================================\r\nKeywords1: ");
                        foreach (Pullenti.Ner.Referent en in ar.Entities)
                        {
                            if (en is Pullenti.Ner.Keyword.KeywordReferent)
                                //Console.WriteLine(en.ToString());
                                //_report.Keywords1 += $"{en}<br>";
                                _article.Keywords1 += $"{en}<br>";
                        }
                        //Console.WriteLine("\r\n==========================================\r\nKeywords2: ");
                        for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
                        {
                            if (t is Pullenti.Ner.ReferentToken)
                            {
                                Pullenti.Ner.Keyword.KeywordReferent kw = t.GetReferent() as Pullenti.Ner.Keyword.KeywordReferent;
                                if (kw == null)
                                    continue;
                                string kwstr = Pullenti.Ner.Core.MiscHelper.GetTextValueOfMetaToken(t as Pullenti.Ner.ReferentToken, Pullenti.Ner.Core.GetTextAttr.FirstNounGroupToNominativeSingle | Pullenti.Ner.Core.GetTextAttr.KeepRegister);
                                //Console.WriteLine($"{kwstr} = {kw}");
                                //_report.Keywords2 += $"{kwstr} = {kw}<br>";
                                _article.Keywords2 += $"{kwstr} = {kw}<br>";
                            }
                        }
                    }                   
                    
                    int res = (from x in npt_tokens
                               select x).Distinct().Count();

                    Console.WriteLine($"npt_tokens.count={npt_tokens.Count}, distinct.Count={res}");
                    Console.WriteLine("Analysis is over!");

                    var query = from x in npt_tokens
                            group x by x into g
                            let count1 = g.Count()
                            orderby count1 descending
                            select new { Name = g.Key, Count = count1 };
                    foreach (var result in query)
                    {
                        _report.NounGroupsSorted += $"{result.Name}, Count: {result.Count}<br>";
                        //Console.WriteLine($"Name: {result.Name}, Count: {result.Count}");                        
                    }

                    //AppData.Report = _report;
                }
                else
                {
                    _status = $"File duplicate founded, hash: {hash}.";
                    Console.WriteLine(_status);

                    _article = new();
                    _articleClone = new();
                    _document = null;
                    _memoryStream.Close();             
                    _modalInfo.Open("Загрузка не удалась, такой документ уже есть в системе.");
                }

                file = null;
                stream.Close();
            }

            _modalLoading.Close();
        }

        async Task NewDocument()
        {
            _article = new();
            _articleClone = new();
            _document = null;
            _memoryStream = null;

            _status = "Blank document created";

            await JsRuntime.InvokeVoidAsync("WidgetMenuClick", ".block__dropbox", "desktop_menu");
            //_counter = 1;
        }

        async Task SaveDocument_OnClick(ArticleStatus articleNewStatus)
        {
            Console.WriteLine($"SaveDocument_OnClick. DocID: {_article.ID}, Status: {_article.Status}");

            if (_article.Name == null || _article.Name.Length == 0)
            {
                Console.WriteLine($"SaveDocument, empty article name. DocID: {_article.ID}, Status: {_article.Status}, filename: {_article.Filename}");
                _modalInfo.Open( $"Для сохранения документа необходимо ввести название.");
                return;
            }

            if (_article.Status == ArticleStatus.New && _memoryStream == null)
            {
                Console.WriteLine($"SaveDocument, empty source file. DocID: {_article.ID}, Status: {_article.Status}, filename: {_article.Filename}");
                _modalInfo.Open($"Для сохранения документа необходимо прикрепить исходный файл.");
                return;
            }

            if (_article.Status == ArticleStatus.New && articleNewStatus == ArticleStatus.Verifying
                || _article.Status == ArticleStatus.AwatingVerify && articleNewStatus == ArticleStatus.Saved
                || _article.Status == ArticleStatus.Verifying && articleNewStatus == ArticleStatus.Saved
                || _article.Status == ArticleStatus.Verified && articleNewStatus == ArticleStatus.Saved
                || _article.Status == ArticleStatus.Verified && articleNewStatus == ArticleStatus.Verifying)
            {
                Console.WriteLine($"SaveDocument, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
                _modalInfo.Open($"Текущий статус документа не позволяет сохранение.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
            }
            else
            {
                /// all is fine, continue
                MySQLConnector dbCon = MySQLConnector.Instance();
                long id;
                string stringSQL;

                if (_article.Status == ArticleStatus.New)
                {
                    _modalLoading.Open();

                    stringSQL = $"INSERT INTO articles (filename, article_name, authors, date_publish, annotation, keywords, file_hash, " +
                        $"doc_noungroups, doc_entities, doc_morph, doc_keywords1, doc_keywords2, char_count) " +
                        $"VALUES ('{_article.Filename}', '{_article.Name}', '{_article.Authors}', '{_article.PublishDate:yyyy-MM-dd}'," +
                        $"'{_article.Annotation}', '{_article.Keywords}', '{_article.HashSum}'," +
                        $"'{_article.NounGroups}', '{_article.Entities}', '{_article.Morph}', '{_article.Keywords1}', '{_article.Keywords2}', '{_article.CharCount}' )";
                    id = await dbCon.SQLInsert(stringSQL);
                    _article.ID = (int)id;

                    ///todo добавлять новый док в массив AppData.Articles

                    stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
                        $"VALUES ('{id}', '{(int)articleNewStatus}', '{_currentAccount.UUID}')";
                    await dbCon.SQLInsert(stringSQL);

                    await SaveFiles((int)id);
                    
                    _article.Status = ArticleStatus.Saved;
                    _articleClone = (ArticleModel)_article.Clone();

                    /// reloading articles
                    await LoadArticles();

                    //_counter = 1;

                    _modalLoading.Close();
                    _modalInfo.Open("Документ успешно создан.");
                }
                else
                {
                    await UpdateDocument(articleNewStatus);
                    //await SaveFiles(_article.ID);

                    if (_article.Status == ArticleStatus.AwatingVerify || _article.Status == ArticleStatus.Verifying)
                       await _article.SaveLastVerificationPoint(_verificationPoint, _currentAccount.UUID);
                }                
            }
        }

        async Task LoadDocument(int docid)
        {
            ///todo загружать из массива AppData.Articles

            Console.WriteLine($"LoadDocument, docid: {docid}.");
            _modalLoading.Open();

            if (docid > 0)
            {
                MySQLConnector dbCon = MySQLConnector.Instance();
                
                AppData.CurrentArticleClone = await dbCon.SQLSelectArticle(docid);
                await AppData.CurrentArticleClone.GetVerificationHistory(_currentAccount.UUID);
                if (AppData.CurrentArticleClone.VerificationHistory.Count > 0)
                    _verificationPoint = AppData.CurrentArticleClone.VerificationHistory.Values.Last();
                else
                    _verificationPoint = new();

                AppData.CurrentArticle = (ArticleModel)AppData.CurrentArticleClone.Clone();                

                //string initiator = await _article.GetInitiatorUUID();
                //initiatorAcc = AccountModel.Find(initiator);
                //_status = $"Article ID={DocID} loaded, status: {_article.Status}, initiator: {initiatorAcc.Name}";

                _status = $"Article ID={docid} loaded, status: {AppData.CurrentArticle.Status}.";

                _article = AppData.CurrentArticle ?? (new());
                _articleClone = AppData.CurrentArticleClone ?? (new());
            }

            _modalLoading.Close();
        }

        async Task SendToVerify_OnClick()
        {
            if (_article.Status == ArticleStatus.New || _article.Status == ArticleStatus.Saved)
            {
                Console.WriteLine($"SendToVerify, DocID: {_article.ID}, Status: {_article.Status}");
                _modalLoading.Open();

                /// form validation
                List<string> errorFields = ValidateForm<ArticleModel>(_article);
                if (errorFields.Count > 0)
                {
                    _modalLoading.Close();
                    string invalid_fields = string.Join(", ", errorFields);
                    _modalInfo.Open($"Не заполнены поля: {invalid_fields}");
                    Console.WriteLine($"SendToVerify. Required fields: '{invalid_fields}' is not filled.");
                    return;
                }

                await UpdateDocument(ArticleStatus.AwatingVerify);

                _modalLoading.Close();
            }
            else
            {
                Console.WriteLine($"SendToVerify, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
                _modalInfo.Open($"Документ не может быть отправлен на верификацию.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
            }            
        }

        async Task Verify_OnClick()
        {
            //if (DocID > 0)
            //{
            //    status = propDict.Count > 0 ? "All changes saved, article has veryfied." : "Article verifyed without any changes.";
            //    transactionId = await Verify();
            //    Console.WriteLine("transactionId found " + transactionId);

            //    ///tmp
            //    editsCount = await article.GetEditsCount(currentAcc.UUID);
            //    modalInfo_transac.Open();
            //}
            if (_article.Status == ArticleStatus.AwatingVerify || _article.Status == ArticleStatus.Verifying)
            {
                Console.WriteLine($"Verify, DocID: {_article.ID}, Status: {_article.Status}");
                _modalLoading.Open();

                /// form validation
                List<string> errorFields = ValidateForm<ArticleModel>(_article);
                if (errorFields.Count > 0)
                {
                    _modalLoading.Close();
                    string invalid_fields = string.Join(", ", errorFields);
                    _modalInfo.Open($"Не заполнены поля: {invalid_fields}");
                    Console.WriteLine($"Verify. Required fields: '{invalid_fields}' is not filled.");
                    return;
                }

                if (!rejectReasonDisable)
                {
                    if (_verificationPoint.RejectReason == null || _verificationPoint.RejectReason.Length == 0)
                    {
                        _modalLoading.Close();
                        _modalInfo.Open($"Не заполнена причина отклонения.");
                        Console.WriteLine($"Verify. Reject reason is not filled.");
                        return;
                    }
                }

                var bcMain = await _currentAccount.GetSelectedBlockChain();
                var transactionId = await bcMain.Verify(_currentAccount, _article);
                Console.WriteLine("transactionId found " + transactionId);
                await UpdateDocument(ArticleStatus.Verified);

                _modalLoading.Close();
            }
            else
            {
                Console.WriteLine($"Verify, wrong status. DocID: {_article.ID}, Status: {_article.Status}");
                _modalInfo.Open($"Документ не может быть верифицирован.<br>DocID: {_article.ID}, Status: {GetDisplayName(_article.Status)}");
            }
        }

        async Task UpdateDocument(ArticleStatus articleStatus)
        {
            _modalLoading.Open();

            MySQLConnector dbCon = MySQLConnector.Instance();
            string stringSQL;
            string rating = (_article.Rating == null) ? "NULL" : _article.Rating.ToString();

            stringSQL = $"UPDATE articles " +
                $"SET filename='{_article.Filename}', article_name='{_article.Name}', authors='{_article.Authors}', " +
                $"date_publish='{_article.PublishDate:yyyy-MM-dd}', annotation='{_article.Annotation}', " +
                $"keywords='{_article.Keywords}', rating={rating}, file_hash='{_article.HashSum}' " +
                $"WHERE id={_article.ID}";
            await dbCon.SQLInsert(stringSQL);

            ///todo обновлять док в массиве AppData.Articles

            stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
                 $"VALUES ('{_article.ID}', '{(int)articleStatus}', '{_currentAccount.UUID}')";
            await dbCon.SQLInsert(stringSQL);

            ///если нужно фиксировать изменение статуса в поле (табл articles_edit_log)
            //_article.Status = articleStatus;

            Dictionary<string, PropertyInfo> propDict = Compare.SimpleCompare<ArticleModel>(_article, _articleClone);
            foreach (KeyValuePair<string, PropertyInfo> prop in propDict)
            {
                //Console.WriteLine($"property name: {prop.Key}, value: {prop.Value.GetValue(articleModel, null)}");

                stringSQL = $"INSERT INTO articles_edit_log (article_id, acc_id, field_name, field_prevvalue, field_newvalue) " +
                    $"VALUES ('{_article.ID}', '{_currentAccount.UUID}', '{prop.Key}', '{prop.Value.GetValue(_articleClone, null)}', '{prop.Value.GetValue(_article, null)}')";
                await dbCon.SQLInsert(stringSQL);
            }

            _article.Status = articleStatus;
            _articleClone = (ArticleModel)_article.Clone();

            /// reloading articles            
            await LoadArticles();

            _modalLoading.Close();

            string message = propDict.Count > 0 ? "Все изменения успешно сохранены." : "Изменения не были найдены при сохранении.";
            if (_article.Status == ArticleStatus.Verified)
                message = "Документ успешно валидирован";

            _modalInfo.Open(message);
        }

        async Task DocSelect_OnChange(ChangeEventArgs e)
        {
            int docid = int.Parse(e.Value.ToString());
            Console.WriteLine($"DocSelect_OnChange. docid: {docid}");

            await LoadDocument(docid);
        }

        async Task SaveFiles(int docid)
        {
            if (_document != null )
            {   
                string fullpath;
                string htmldirectorypath;
                string docdirectorypath;
#if DEBUG
                htmldirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
                docdirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
#else
                htmldirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
                docdirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
#endif
                try
                {
                    /// получаем html, сохраняем файлы
                    GetHtmlParam htmlParams = new();
                    htmlParams.OutHtmlAndBodyTags = true;
                    string html = _document.GetHtmlString(htmlParams);

                    ///saving html
                    fullpath = Path.Combine(htmldirectorypath, $"{docid}_{_document.SourceFileName}.html");
                    Console.WriteLine($"Saving file [{fullpath}]");
                    Directory.CreateDirectory(htmldirectorypath);
                    File.WriteAllBytes(fullpath, Encoding.UTF8.GetBytes(html));
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{ex.GetType()}\r\n{ex.Message}\r\n{ex.StackTrace}");
                }

                ///saving original files
                fullpath = Path.Combine(docdirectorypath, $"{docid}_{_article.Filename}");
                Console.WriteLine($"Saving file [{fullpath}]");
                Directory.CreateDirectory(docdirectorypath);
                FileStream fs = new(fullpath, FileMode.Create, FileAccess.Write);
                _memoryStream.Position = 0;
                await _memoryStream.CopyToAsync(fs);

                _status = $"User has saved new article data: [{docid}_{_article.Filename}], memory size:{_memoryStream.Length}b, file size: {fs.Length}b";
                Console.WriteLine(_status);
                _memoryStream.Close();
                fs.Close();
                _document = null;
            }
        }

        async Task GetArticleFile(string option)
        {
            string fullpath;
            string htmldirectorypath;
            string docdirectorypath;
#if DEBUG
            htmldirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
            docdirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
#else
            htmldirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
            docdirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
#endif

            if (option == "source")
            {
                fullpath = Path.Combine(docdirectorypath, $"{_article.ID}_{_article.Filename}");
                if (fullpath.Length > 0 && File.Exists(fullpath))
                    NavigationManager.NavigateTo($"{STORAGE_FOLDER_NAME}/source/{_article.ID}_{_article.Filename}", true);
                else
                {
                    Console.WriteLine($"GetArticleFile, file not exists: [{fullpath}].");
                    _modalInfo.Open("Файл документа не найден.");
                }                
            }
                
            else if (option == "html")
            {
                fullpath = Path.Combine(docdirectorypath, $"{_article.ID}_{_article.Filename}.html");

                if (fullpath.Length > 0 && File.Exists(fullpath))
                    NavigationManager.NavigateTo($"{STORAGE_FOLDER_NAME}/html/{_article.ID}_{_article.Filename}.html", true);
                else
                {
                    Console.WriteLine($"GetArticleFile, file not exists: [{fullpath}].");
                    _modalInfo.Open("Файл документа не найден.");
                }
            }            
            else
            {
                Console.WriteLine($"GetArticleFile, unknown method option: [{option}].");
                //return;
            }            
        }

        async Task LoadArticles()
        {
            /// reloading articles            
            await AppData.LoadArticles();
            _articles = new();

            ///updating local articles and UI on role claims
            if (_currentAccount.Roles.Contains(Role.Admin))
            {
                loadButtonDisable = false;
                verifyButtonDisable = false;

                int counter = 1;
                foreach (ArticleModel article in AppData.Articles.Values) { _articles.Add(counter++, article); }
            }
            else if (_currentAccount.Roles.Contains(Role.Expert))
            {
                loadButtonDisable = true;
                verifyButtonDisable = false;

                int counter = 1;
                foreach (ArticleModel article in AppData.Articles.Values)
                {
                    if (article.Status == ArticleStatus.AwatingVerify || article.Status == ArticleStatus.Verifying || article.Status == ArticleStatus.Verified)
                        _articles.Add(counter++, article);
                }

                SwitchDesktopTab(1);
            }
            else if (_currentAccount.Roles.Contains(Role.Miner))
            {
                loadButtonDisable = false;
                verifyButtonDisable = true;

                int counter = 1;
                foreach (ArticleModel article in AppData.Articles.Values)
                {
                    if (article.Status == ArticleStatus.New || article.Status == ArticleStatus.Saved || article.Status == ArticleStatus.Verified)
                        _articles.Add(counter++, article);
                }
            }
            else
            {
                loadButtonDisable = true;
                verifyButtonDisable = true;

                int counter = 1;
                foreach (ArticleModel article in AppData.Articles.Values) { _articles.Add(counter++, article); }

                SwitchDesktopTab(2);
            }
        }

        async Task<string> CalculateHashSum(MemoryStream ms)
        {
            MD5CryptoServiceProvider md5Provider = new();
            ms.Position = 0;
            byte[] hash = await md5Provider.ComputeHashAsync(ms);
            return Convert.ToBase64String(hash);
        }

        List<string> ValidateForm<T>(T obj)
        {
            var props = typeof(T).GetProperties().Where(pi => Attribute.IsDefined(pi, typeof(RequiredAttribute)));
            List<string> result = new();
            foreach (var prop in props)
            {
                var val = prop.GetValue(obj, null);
                if (val == null || val?.ToString().Length == 0)
                    result.Add(prop.Name);

                //Console.WriteLine($"Required field '{prop.Name}' is not filled.");
            }
            return result;
        }

        static string GetDisplayName(Enum enumValue)
        {
            return enumValue.GetType()
                            .GetMember(enumValue.ToString())
                            .First()
                            .GetCustomAttribute<DisplayAttribute>()
                            .GetName();
        }

        async Task<AccountModel> GetCurrentAcc()
        {
            Console.WriteLine($"Desktop GetCurrentAcc");

            if (_currentAccount == null)
            {
                var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
                var user = authState.User;

                if (user.Identity.IsAuthenticated)
                {
                    var currentUser = await UserManager.GetUserAsync(user);
                    var acc = AccountModel.Find(currentUser.Id);
                    if (acc != null)
                    {
                        _currentAccount = AccountModel.Loaded[currentUser.Id];
                    }
                    else
                    {
                        Dictionary<string, AccountModel> accounts = await MySQLConnector.Instance().SQLSelectASPUsers();
                        if (accounts.ContainsKey(currentUser.Id))
                            _currentAccount = accounts[currentUser.Id];
                    }
                    /// asp roles
                    //var role1 = new IdentityRole { Name = "admin" };
                    //var result = await RoleManager.CreateAsync(role1);
                    //Console.WriteLine($"RoleManager create result: {result}");
                    //result = await UserManager.AddToRoleAsync(currentUser, role1.Name);
                    //Console.WriteLine($"RoleManager add role result: {result}");
                }
            }
            return _currentAccount;
        }
    }
}