#include "stdafx.h"

#include "xtr/xtrProcedures.h"

#include "resource.h"

#include "xtr/Exteor.h"
#include "xtr/ExteorOptions.h"
#include "xtr/cclDescriptor.h"

#include "xtr/doc/Documents.h"
#include "xtr/doc/RSFacade.h"
#include "xtr/doc/OSSDoc.h"

#include "xtr/io/SchemaExporter.h"
#include "xtr/io/CstGraphExporter.h"
#include "xtr/io/ModelDataExchange.h"
#include "xtr/io/OSSExporter.h"
#include "xtr/io/ModelExporter.h"

#include "xtr/dialog/BasicDlg.h"
#include "xtr/dialog/ExportSchema.h"
#include "xtr/dialog/ExportModel.h"
#include "xtr/dialog/ExportCstGraphDlg.h"
#include "xtr/dialog/OpSyntGUI.h"
#include "xtr/dialog/CstRenameDialog.h"
#include "xtr/dialog/Term2Function.h"
#include "xtr/dialog/Function2Term.h"
#include "xtr/dialog/GenerateStructure.h"
#include "xtr/dialog/PickOperation.h"
#include "xtr/dialog/EditConstituenta.h"

#include "xtr/view/OSSLayout.h"

#include "ccl/semantic/CstFilters.hpp"
#include "ccl/rslang/RSExpr.h"

namespace xtr::proc {

using ccl::semantic::CstType;
using ccl::semantic::RSForm;
using ccl::semantic::RSCore;
using ccl::semantic::RSModel;
using ccl::oss::OSSchema;
using xtr::doc::RSFormDoc;
using xtr::doc::RSModelDoc;
using xtr::doc::OSSDoc;
using xtr::dialog::EditConstituentaDlg;

namespace {

using ccl::ops::EquationOptions;
using ccl::oss::PictID;

CString PathForExport(const CString sourcePath, const CString suffix, const FileType type) {
  const auto source = std::filesystem::path(sourcePath.GetString());
  assert(std::filesystem::exists(source));

  const auto directory = source.parent_path() / L"export";
  if (!std::filesystem::exists(directory)) {
    std::filesystem::create_directory(directory);
  }
  auto exportPath = directory / source.filename().replace_extension();
  if (!suffix.IsEmpty()) {
    exportPath += L"_" + suffix;
  }
  exportPath += L"_" + CTime::GetCurrentTime().Format(LR"(%Y%m%d_%H%M)") + ExtensionFor(type);
  return exportPath.c_str();
}

BOOL ConfirmURL(CString url) {
  return (url.Right(4).CompareNoCase(LR"(.exe)") != 0 && url.Right(4).CompareNoCase(LR"(.bat)") != 0)
    || ::AfxMessageBox(mfc::FormatSID(IXTRS_URL_CONFIRM, url.GetString()), MB_YESNO | MB_ICONQUESTION) == IDYES;
}

BOOL ImportStructured(ccl::semantic::RSModel& model, const EntityUID target) {
  const auto* typif = model.GetParse(target).Typification();
  if (typif == nullptr) {
    ::AfxMessageBox(IXTRE_RSMODEL_TYPIFICATION, MB_ICONERROR);
    return FALSE;
  }

  const auto result = io::RSMDataX::ImportStructured(model, *typif);
  if (!result.has_value()) {
    return FALSE;
  }

  const auto newData = result->Unpack(*typif);
  if (!newData.has_value()) {
    ::AfxMessageBox(IXTRE_RSMODEL_DATA, MB_ICONERROR);
    return FALSE;
  }

  const auto oldData = model.Values().SDataFor(target);
  const auto merge = typif->IsCollection() && oldData.has_value() && !oldData->B().IsEmpty()
    && ::AfxMessageBox(IXTRS_PROMT_MERGE_DATA, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES;
  model.Values().SetStructureData(target, merge ? oldData->B().Union(newData->B()) : newData.value());
  return TRUE;
}

BOOL ImportNamed(ccl::semantic::RSModel& model, const EntityUID target) {
  if (const auto result = io::RSMDataX::ImportText(model); result.has_value()) {
    model.Values().SetBasicText(target, result.value());
    return TRUE;
  } else {
    return FALSE;
  }
}

std::pair<bool, std::unique_ptr<EquationOptions>>
RequestOperationOpts(OSSchema& oss, const PictID pid, const ccl::ops::Type operation) {
  static constexpr BOOL runFromOSS = TRUE;

  if (operation == ccl::ops::Type::tba) {
    return { false, nullptr };
  }
  const auto parents = oss.Graph().ParentsOf(pid);
  const auto* operand1 = ccl::src::CastData<RSForm>(oss.Src().DataFor(parents.at(0)));
  const auto* operand2 = ccl::src::CastData<RSForm>(oss.Src().DataFor(parents.at(1)));
  if (operand1 == nullptr || operand2 == nullptr) {
    ::AfxMessageBox(IXTRE_EDIT_OPT_NO_ARG, MB_ICONEXCLAMATION);
    return { false, nullptr };
  }

  const auto& pict = *(oss)(pid);
  const auto* oldEquations = dynamic_cast<const EquationOptions*>(oss.Ops()(pid)->options.get());
    
  dialog::SynthesUI dlgSynt(operation, runFromOSS);
  dlgSynt.operand1 = operand1;
  dlgSynt.operand2 = operand2;
  dlgSynt.title = mfc::ToMFC(pict.title);
  dlgSynt.alias = mfc::ToMFC(pict.alias);
  if (oldEquations != nullptr && ccl::ops::HasOptions(operation)) {
    dlgSynt.equations = *oldEquations;
  }

  if (dlgSynt.DoModal() == IDCANCEL) {
    return { false, nullptr };
  }

  oss.SetPictTitle(pid, mfc::ToSTL(dlgSynt.title));
  oss.SetPictAlias(pid, mfc::ToSTL(dlgSynt.alias));
  if (!ccl::ops::HasOptions(operation)) {
    return { true, nullptr };
  } else {
    return { true, std::make_unique<EquationOptions>(dlgSynt.equations) };
  }
}

std::optional<CString> CreateOssArchive(const OSSDoc& document) {
  const auto basePath = std::filesystem::path(document.OSS().Src().ossDomain);
  auto savePath = basePath  / mfc::ToUTF8(CTime::GetCurrentTime().Format(LR"(%Y%m%d_%H%M)"));
  if (std::filesystem::exists(savePath) || !std::filesystem::create_directory(savePath)) {
    ::AfxMessageBox(mfc::FormatSID(IXTRE_FOLDER_CREATE_FAIL, mfc::ToMFC(savePath.u8string()).GetString()), MB_ICONINFORMATION);
    return std::nullopt;
  }

  const auto ossFileName = std::filesystem::path(mfc::ToUTF8(document.GetPathName())).filename();
  const auto ossOldPath = basePath / ossFileName;
  const auto ossArchPath = savePath / ossFileName;
  std::filesystem::copy_file(ossOldPath, ossArchPath);

  for (const auto& pict : document.OSS()) {
    const auto* handle = document.OSS().Src()(pict.uid);
    if (!std::empty(*handle)) {
      const auto oldPath = basePath / handle->desc.name;
      const auto archPath = savePath / handle->desc.name;
      if (std::filesystem::exists(oldPath)) {
        std::filesystem::copy_file(oldPath, archPath);
      }
    }
  }

  return mfc::ToMFC(savePath.u8string());
}

BOOL RunOperation(OSSchema& oss, const PictID opid, const BOOL showResult = TRUE) {
  if (!oss.Ops().Execute(opid)) {
    ::AfxMessageBox(IXTRS_EXECUTION_FAILED, MB_ICONEXCLAMATION);
    return FALSE;
  }

  if (auto* doc = dynamic_cast<RSFormDoc*>(oss.Src().OpenSrc(opid));
      doc == nullptr) {
    return FALSE;
  } else {
    if (showResult) {
      ExteorApplication::Docs().ShowDocument(*doc);
    }
    return TRUE;
  }
}

CString PromptForSourceFile(const CString folder) {
  CFileDialog dialog(TRUE, LR"(*.trs)", LR"(*.trs)", OFN_HIDEREADONLY | OFN_EXPLORER, mfc::LoadSID(IXTRS_FILTER_OSS_PROMPT));
  const auto title = mfc::LoadSID(IXTRS_PICT_NAME_AT);
  dialog.m_ofn.lpstrInitialDir = folder;
  dialog.m_ofn.lpstrTitle = title;
  if (dialog.DoModal() == IDOK) {
    return dialog.GetPathName();
  } else {
    return CString{};
  }
}

BOOL CheckLocalCstUse(const RSCore& core, const SetOfEntities& target) {
  const auto dependant = core.RSLang().Graph().ExpandOutputs(target);
  SetOfEntities difference{};
  std::copy_if(dependant.begin(), dependant.end(),
               std::inserter(difference, begin(difference)),
               [&target](const auto needle) { return target.find(needle) == target.end(); });
  if (std::empty(difference)) {
    return true;
  }
  CString cstList{};
  for (const auto entity : core.List().SortSubset(difference)) {
    cstList += mfc::ToMFC(core.GetRS(entity).alias);
    cstList += ' ';
  }
  return ::AfxMessageBox(mfc::FormatSID(IXTRS_DEL_CST_LOCAL_USE, cstList.GetString()),
                       MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION) == IDYES;
}

BOOL CheckCstUseInOSS(const RSFormDoc& document, const SetOfEntities& target) {
  for (const auto entity : target) {
    switch (ccl::Environment::Sources().QueryEntityUse(document, entity)) {
    case ccl::change::Usage::asElement: {
      auto msg = mfc::FormatSID(IXTRS_DELCST_USED_IN_OSS, mfc::ToMFC(document.Schema().GetRS(entity).alias).GetString());
      if (::AfxMessageBox(msg, MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION) != IDYES) {
        return FALSE;
      } else {
        break;
      }
    }
    case ccl::change::Usage::asOption: {
      auto msg = mfc::FormatSID(IXTRS_DELCST_USED_IN_OPS, mfc::ToMFC(document.Schema().GetRS(entity).alias).GetString());
      ::AfxMessageBox(msg, MB_ICONEXCLAMATION);
      return FALSE;
    }
    case ccl::change::Usage::notUsed:;
    }
  }
  return TRUE;
}

std::vector<CString> ParseArguments(const CString args) {
  std::vector<CString> result{};
  int pos = 0;
  while (true) {
    auto token = args.Tokenize(LR"(<>)", pos);
    if (token.IsEmpty()) {
      return result;
    }
    if (token.GetLength() > 1) {
      result.push_back(token);
    }
  }
}

} // anonymous namespace

// ========================================================

void ExportCstGraph(const RSCore& source, const CString path) {
  dialog::ExportCstGraphDlg dlg{};
  if (dlg.DoModal() != IDOK) {
    return;
  }
  const auto exportPath = PathForExport(path, L"Graph", dlg.exportType);
  const auto success = io::CstGraphExporter(source).Export(dlg.options, exportPath, dlg.exportType);
  if (!success) {
    ::AfxMessageBox(mfc::FormatSID(IXTRE_FILE_UNAVAILABLE, exportPath.GetString()), MB_ICONERROR);
  } else {
    ::ShellExecuteW(nullptr, L"open", exportPath, nullptr, nullptr, SW_SHOW);
    ::AfxMessageBox(mfc::FormatSID(IXTRS_EXPORT_SUCCESS, exportPath.GetString()), MB_ICONINFORMATION);
  }
}

void ExportSchema(const RSForm& schema, const CString path) {
  dialog::ExportSchema dlg{};
  if (dlg.DoModal() != IDOK) {
    return;
  }
  const auto exportPath = PathForExport(path, L"Schema", dlg.exportType);
  const auto success = io::SchemaExporter(schema).Export(dlg.options, exportPath, dlg.exportType);
  if (!success) {
    if (dlg.exportType == FileType::docx) {
      ::AfxMessageBox(IXTRE_EXPORT_WORD, MB_ICONERROR);
    } else {
      ::AfxMessageBox(mfc::FormatSID(IXTRE_FILE_UNAVAILABLE, exportPath.GetString()), MB_ICONINFORMATION);
    }
  } else {
    if (dlg.exportType == FileType::docx) {
      ::AfxMessageBox(IXTRS_EXPORT_WORD_OK, MB_ICONINFORMATION);
    } else {
      ::ShellExecuteW(nullptr, L"open", exportPath, nullptr, nullptr, SW_SHOW);
      ::AfxMessageBox(mfc::FormatSID(IXTRS_EXPORT_SUCCESS, exportPath.GetString()), MB_ICONINFORMATION);
    }
  }
}

void ExportModelSchema(const RSModel& model, const CString path) {
  RSForm schema{};
  schema.LoadCore() = model.Core();
  schema.title = model.title;
  schema.alias = model.alias;
  schema.comment = model.comment;
  ExportSchema(schema, path);
}

void ExportOSS(const doc::OSSDoc& document) {
  const auto exportPath = PathForExport(document.GetPathName(), L"OSS", FileType::json);
  const auto success = io::OSSExporter(document.OSS()).Export(exportPath, FileType::json);
  if (!success) {
    ::AfxMessageBox(mfc::FormatSID(IXTRE_FILE_UNAVAILABLE, exportPath.GetString()), MB_ICONERROR);
  } else {
    ::ShellExecuteW(nullptr, L"open", exportPath, nullptr, nullptr, SW_SHOW);
    ::AfxMessageBox(mfc::FormatSID(IXTRS_EXPORT_SUCCESS, exportPath.GetString()), MB_ICONINFORMATION);
  }
}

void ExportOSSGraph(const OSSDoc& document, view::OSSLayout& layout) {
  const auto exportPath = PathForExport(document.GetPathName(), L"OSSGraph", FileType::png);
   const auto image = layout.GetImage();
  if (image.Save(exportPath) != S_OK) {
    ::AfxMessageBox(IXTRE_OSS_PNG_FAIL, MB_ICONEXCLAMATION);
  } else {
    ::ShellExecuteW(nullptr, L"open", exportPath, nullptr, nullptr, SW_SHOW);
    ::AfxMessageBox(mfc::FormatSID(IXTRS_EXPORT_SUCCESS, exportPath.GetString()), MB_ICONINFORMATION);
  }
}

void ExportModel(const doc::RSModelDoc& document) {
  dialog::ExportModel dlg{};
  if (dlg.DoModal() != IDOK) {
    return;
  }
  const auto exportPath = PathForExport(document.GetPathName(), L"Model", dlg.exportType);
  const auto success = io::ModelExporter(document.Model()).Export(exportPath, dlg.exportType);
  if (!success) {
    if (dlg.exportType == FileType::xlsx) {
      ::AfxMessageBox(IXTRS_EXPORTXL_FAIL, MB_ICONERROR);
    } else {
      ::AfxMessageBox(mfc::FormatSID(IXTRE_FILE_UNAVAILABLE, exportPath.GetString()), MB_ICONERROR);
    }
  } else {
    ::ShellExecuteW(nullptr, L"open", exportPath, nullptr, nullptr, SW_SHOW);
    if (dlg.exportType == FileType::xlsx) {
      ::AfxMessageBox(IXTRS_EXPORTXL_SUCCESS, MB_ICONINFORMATION);
    } else {
      ::AfxMessageBox(mfc::FormatSID(IXTRS_EXPORT_SUCCESS, exportPath.GetString()), MB_ICONINFORMATION);
    }
  }
}

void ExportCstDataToExcel(const ccl::semantic::RSModel& model, EntityUID target) {
  if (!io::RSMDataX::ExportDataFor(target, model)) {
    ::AfxMessageBox(IXTRS_EXPORTXL_FAIL, MB_ICONERROR);
  }
}

void EraseCstData(ccl::semantic::RSModel& model, const EntityUID target) {
  const auto type = model.GetRS(target).type;
  if (!ccl::semantic::IsBaseNotion(type)) {
    return;
  } else if (::AfxMessageBox(IXTRS_PROMT_ERASE_DATA, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES) {
    model.Values().ResetDataFor(target);
  }
}

void ImportCstDataFromExcel(ccl::semantic::RSModel& model, EntityUID target) {
  if (ccl::semantic::IsBaseSet(model.GetRS(target).type) ?
      ImportNamed(model, target) : ImportStructured(model, target)) {
    ::AfxMessageBox(IXTRS_RSMODEL_DATA_IMPORT_SUCCESS, MB_ICONINFORMATION);
  } else {
    ::AfxMessageBox(IXTRS_RSMODEL_DATA_IMPORT_FAIL, MB_ICONERROR);
  }
}

void EditConstituenta(doc::RSFacade& editor, const EntityUID target) {
  EditConstituentaDlg(editor, target).DoModal();
  editor.Document().UpdateAllViews(nullptr);
}

void RenameCst(doc::RSFacade& editor) {
  auto dlg = dialog::CstRename(editor.Core(), *std::begin(editor.Document().Selection()));
  if (dlg.DoModal() != IDOK) {
    return;
  }
  if (dlg.newName == dlg.oldName) {
    ::AfxMessageBox(IXTRE_RENAME_NOOP, MB_ICONEXCLAMATION);
    return;
  }

  const auto target = editor.Core().FindAlias(mfc::ToSTL(dlg.oldName)).value();
  if (!editor.SetAliasFor(target, mfc::ToSTL(dlg.newName), dlg.doUpdateExpr)) {
    ::AfxMessageBox(IXTRE_RENAME_FAIL, MB_ICONEXCLAMATION);
  } else {
    editor.Document().UpdateAllViews(nullptr);
    ::AfxMessageBox(IXTRS_RENAME_SUCCESS, MB_ICONINFORMATION);
  }
}

void Term2Function(doc::RSFacade& editor) {
  dialog::Term2Function dlg(editor, *std::begin(editor.Document().Selection()));
  dlg.DoModal();
  if (!empty(dlg.addedCst)) {
    editor.Document().SetSelection({ std::begin(dlg.addedCst), std::end(dlg.addedCst) });
    editor.Document().UpdateAllViews(nullptr);
  }
}

void Function2Term(doc::RSFacade& editor) {
  dialog::Func2Term dlg(editor, *std::begin(editor.Document().Selection()));
  dlg.DoModal();
  if (!empty(dlg.addedCst)) {
    editor.Document().SetSelection({ std::begin(dlg.addedCst), std::end(dlg.addedCst) });
    editor.Document().UpdateAllViews(nullptr);
  }
}

void Structure2Terms(doc::RSFacade& editor) {
  dialog::Structure2Terms dlg(editor, *std::begin(editor.Document().Selection()));
  dlg.DoModal();
  if (!empty(dlg.addedCst)) {
    editor.Document().SetSelection({ std::begin(dlg.addedCst), std::end(dlg.addedCst) });
    editor.Document().UpdateAllViews(nullptr);
  }
}

void MoveCstBefore(doc::RSFacade& editor, const SetOfEntities& what, const ccl::semantic::ListIterator pos) {
  for (const auto entity : editor.List().SortSubset(what)) {
    if (!editor.MoveBefore(entity, pos)) {
      ::AfxMessageBox(IXTRS_ERR_MOVE_CST, MB_ICONERROR);
      break;
    }
  }
  editor.Document().SetSelection(what);
  editor.Document().UpdateAllViews(nullptr);
}

void EditComment(doc::RSFacade& editor) {
  const std::string oldComment = editor.IsModel() ? editor.Model().comment : editor.Schema().comment;
  dialog::EditComment editDlg(mfc::ToMFC(oldComment), mfc::LoadSID(IXTRS_COMMENT));
  if (editDlg.DoModal() != IDOK) {
    return;
  }
  if (const auto newComment = mfc::ToSTL(editDlg.comment); oldComment != newComment) {
    editor.SetComment(newComment);
    editor.Document().SetModifiedFlag(TRUE);
    editor.Document().UpdateAllViews(nullptr);
  }
}

void ResetNames(doc::RSFacade& editor) {
  editor.ResetAliases();
  editor.Document().UpdateAllViews(nullptr);
}

void StandaloneEquation(RSFormDoc& document) {
  dialog::EditString dlg(LR"(X1>X2)", mfc::LoadSID(IXTRS_CAPTION_EQUATE));
  if (dlg.DoModal() != IDOK) {
    return;
  }

  const auto mergeList = ParseArguments(dlg.resultStr);
  if (ssize(mergeList) != 2) {
    ::AfxMessageBox(IXTRE_EQUATION_ARGS, MB_ICONERROR);
    return;
  }

  auto& schema = document.Schema();
  const auto id1 = schema.Core().FindAlias(mfc::ToSTL(mergeList.at(0)));
  const auto id2 = schema.Core().FindAlias(mfc::ToSTL(mergeList.at(1)));
  if (!id1.has_value() || !id2.has_value()) {
    ::AfxMessageBox(IXTRE_EQUATION_UNKNOWN, MB_ICONERROR);
    return;
  }

  const auto equationMode = [&]() noexcept {
    if (dlg.resultStr.Find(L"<") != mfc::noItems) {
      return ccl::ops::Equation::Mode::keepHier;
    } else {
      return ccl::ops::Equation::Mode::keepDel;
    }
  }();

  ccl::ops::EquationOptions equations{};
  equations.Insert(id1.value(), id2.value(), ccl::ops::Equation{ equationMode, std::string{} });
  if (!schema.Ops().IsEquatable(equations)) {
    ::AfxMessageBox(IXTRE_EQUATION_INVALID, MB_ICONERROR);
  } else if (schema.Ops().Equate(equations).has_value()) {
    document.ResetSelection();
    document.UpdateAllViews(nullptr);
  }
}

void ResetClaims(RSFormDoc& document) {
  if (::AfxMessageBox(IXTRS_PROMT_RESET_FLAGS, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES) {
    document.ReleaseClaim();
    document.Schema().Mods().ResetAll();
    document.UpdateAllViews(nullptr);
  }
}

void EraseSelected(RSFormDoc& document) {
  const auto& target = document.Selection();
  for (const auto entity : target) {
    if (document.Schema().Mods().IsTracking(entity)) {
      ::AfxMessageBox(IXTRE_DELETE_CST_PARTIAL, MB_ICONERROR);
      return;
    }
  }

  if (!CheckLocalCstUse(document.Schema().Core(), target) ||
      !CheckCstUseInOSS(document, target)) {
    return;
  }

  for (const auto entity : target) {
    document.Schema().Erase(entity);
  }
  document.ResetSelection();
  document.UpdateAllViews(nullptr);
}

void EraseSelected(RSModelDoc& document) {
  const auto& target = document.Selection();
  if (!CheckLocalCstUse(document.Model().Core(), target)) {
    return;
  }
  for (const auto entity : target) {
    document.Model().Erase(entity);
  }
  document.ResetSelection();
  document.UpdateAllViews(nullptr);
}

void CalculateSelected(RSModelDoc& document) {
  const auto& selection = document.Selection();
  g_Exteor.BeginWaitCursor();
  for (const auto entity : document.Model().List().SortSubset(selection)) {
    document.Model().Calculations().Calculate(entity);
  }
  g_Exteor.EndWaitCursor();
  ::AfxMessageBox(IXTRS_CALCULATION_SUCCESS, MB_ICONASTERISK);
  document.UpdateAllViews(nullptr);
}

void CalculateAll(RSModelDoc& document) {
  document.DoFileSave();

  g_Exteor.BeginWaitCursor();

  const auto start = clock();
  document.Model().Calculations().RecalculateAll();
  const auto duration = clock() - start;

  g_Exteor.EndWaitCursor();

  std::stringstream ss{};
  ss << std::setprecision(4) << static_cast<double>(duration) / CLOCKS_PER_SEC;
  ::AfxMessageBox(mfc::FormatSID(IXTRS_CALC_TIME, mfc::ToMFC(ss.str()).GetString()), MB_ICONASTERISK);

  document.UpdateAllViews(nullptr);
}

void DoAction(OSSDoc& document) {
  auto& oss = document.OSS();
  const auto active = document.selection;
  if (!std::empty(*oss.Src()(active)) || !oss.Ops().HasOperation(active)) {
    proc::OpenSelectedSource(document);
    return;
  } 

  switch (oss.Ops().StatusOf(active)) {
  case ccl::ops::Status::outdated:
  case ccl::ops::Status::done:  proc::OpenSelectedSource(document); break;
  case ccl::ops::Status::undefined: proc::EditSelectedOperation(document); break;
  case ccl::ops::Status::defined: {
    const auto msg = mfc::FormatSID(IXTRS_OSS_SINTEZ_GO, info::Label(oss.Ops()(active)->type).GetString());
    if (::AfxMessageBox(msg, MB_YESNO | MB_ICONQUESTION) == IDYES) {
      proc::RunSelectedOperation(document);
    } else {
      proc::OpenSelectedSource(document);
    }
    break;
  }
  case ccl::ops::Status::broken: {
    if (::AfxMessageBox(IXTRS_BROKEN_OP, MB_YESNO | MB_ICONQUESTION) == IDYES) {
      proc::EditSelectedOperation(document);
    } else {
      proc::OpenSelectedSource(document);
    }
    break;
  }
  }
}

void EditSelectedOperation(OSSDoc& document) {
  auto& oss = document.OSS();
  const auto active = document.selection;
  if (!std::empty(*oss.Src()(active)) &&
      ::AfxMessageBox(IXTRS_CHANGE_OPTS_CAUTION, MB_YESNO | MB_ICONEXCLAMATION) != IDYES) {
    return;
  }

  dialog::PickOperationDlg operationTypeDlg(oss, active);
  if (operationTypeDlg.DoModal() != IDOK) {
    return;
  }
  
  const auto operation = operationTypeDlg.GetSelectedOperation();
  auto [flag, options] = RequestOperationOpts(oss, active, operation);
  if (!flag) {
    return;
  }
  
  if (!oss.Ops().InitFor(active, operation, std::move(options))) {
    ::AfxMessageBox(IXTRS_CCL_INIT_OP_FAIL, MB_ICONERROR);
  } else {
    if (operation != ccl::ops::Type::tba) {
      if (oss.Ops()(active)->broken) {
        ::AfxMessageBox(IXTRS_OPTS_BROKEN, MB_ICONEXCLAMATION);
      } else if (operationTypeDlg.autorun && !RunOperation(oss, active, FALSE)) {
        ::AfxMessageBox(IXTRS_EXECUTE_OP_FAIL, MB_ICONEXCLAMATION);
      }
    }
    document.UpdateAllViews(nullptr);
  }
}

void EditSelectedHyperlink(OSSDoc& document) {
  const auto& pict = *document.OSS()(document.selection);
  if (dialog::EditHyperlink editDlg(pict.lnk); editDlg.DoModal() == IDOK) {
    document.OSS().SetPictLink(pict.uid, { mfc::ToSTL(editDlg.address), mfc::ToSTL(editDlg.subAddr) });
    document.UpdateAllViews(nullptr);
  }
}

void EditActiveFilename(OSSDoc& document) {
  const auto pid = document.selection;
  auto& sources = document.OSS().Src();
  if (auto* doc = dynamic_cast<RSFormDoc*>(sources.OpenSrc(pid)); doc != nullptr) {
    doc->DoFileSave();
    doc->OnCloseDocument();
  }
  if (const auto* handle = sources(pid);  handle == nullptr) {
    ::AfxMessageBox(IXTRE_PICT_NO_SRC, MB_ICONEXCLAMATION);
  } else if (handle->src != nullptr) {
    ::AfxMessageBox(IXTRE_PICT_RENAME_OPENED, MB_ICONEXCLAMATION);
  } else {
    dialog::EditString editDlg{ mfc::ToMFC(std::filesystem::path(handle->desc.name).replace_extension("").u8string()) };
    if (editDlg.DoModal() == IDOK) {
      auto newName = std::filesystem::path(mfc::ToUTF8(editDlg.resultStr));
      newName.replace_extension(mfc::ToUTF8(ExtensionFor(FileType::trs)));
      sources.Rename(pid, newName.u8string());
    }
  }
}

void OpenSelectedSource(OSSDoc& document) {
  const auto active = document.selection;
  auto& oss = document.OSS();
  if (auto* openedDoc = dynamic_cast<RSFormDoc*>(oss.Src().OpenSrc(active)); 
      openedDoc != nullptr) {
    ExteorApplication::Docs().ShowDocument(*openedDoc);
    document.UpdateAllViews(nullptr);
    return;
  } 
  
  if (const auto& srcName = oss.Src()(active)->desc.name; !empty(srcName)) {
    const auto msg = mfc::FormatSID(IXTRE_OSS_SOURCE_UNACCESSIBLE, mfc::ToMFC(srcName).GetString());
    if (::AfxMessageBox(msg, MB_YESNO | MB_ICONEXCLAMATION) == IDYES) {
      oss.Src().Discard(active);
      document.UpdateAllViews(nullptr);
    }
    return;
  } 
  
  if (oss.Ops().HasOperation(active)) {
    return;
  } 
  
  if (::AfxMessageBox(IXTRS_OSS_CREATE_NEW_FILE, MB_YESNO | MB_ICONQUESTION) == IDYES) {
    const auto silence = XTROptions::App().GuardSilence();
    if (auto* newDoc = ExteorApplication::Docs().NewRSFormDoc();
        newDoc != nullptr && newDoc->DoFileSave()) {
      oss.Src().ConnectSrc2Pict(active, *newDoc);
      newDoc->UpdateAllViews(nullptr);
    }
    document.UpdateAllViews(nullptr);
  }
}

void EraseSelected(OSSDoc& document) {
  const auto active = document.selection;
  auto& oss = document.OSS();
  if (!std::empty(oss.Graph().ChildrenOf(active))) {
    ::AfxMessageBox(IXTRS_ERASE_PICT_FAIL, MB_ICONEXCLAMATION);
    return;
  }
  if (std::empty(*oss.Src()(active)) ||
      ::AfxMessageBox(IXTRS_ERASE_SRC_CONFIRM, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES) {
    if (oss.Erase(active)) {
      document.UpdateAllViews(nullptr);
    }
  }
}

void DiscardSelectedSource(OSSDoc& document) {
  if (::AfxMessageBox(IXTRS_DISCARD_SRC_CONFIRM, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) != IDYES) {
    return;
  }
  document.OSS().Src().Discard(document.selection);
  document.UpdateAllViews(nullptr);
}

void RunSelectedOperation(OSSDoc& document) {
  if (document.DoFileSave()
      && RunOperation(document.OSS(), document.selection)) {
    document.UpdateAllViews(nullptr);
  }
}

void AttachSourceToSelected(OSSDoc& document) {
  static constexpr auto noView = FALSE;
  const auto active = document.selection;
  auto& sources = document.OSS().Src();

  if (!std::empty(*sources(active)) &&
      ::AfxMessageBox(IXTRS_IS_CHANGE_TRS_NAME, MB_YESNOCANCEL | MB_ICONQUESTION | MB_DEFBUTTON2) != IDYES) {
    return;
  } 
  
  const auto srcPath = std::filesystem::path(mfc::ToUTF8(PromptForSourceFile(mfc::ToMFC(sources.ossDomain))));
  if (srcPath.empty()) {
    return;
  }
  const auto newPath = std::filesystem::path(sources.ossDomain) / srcPath.filename();
  if (srcPath != newPath && !std::filesystem::copy_file(srcPath, newPath)) {
    ::AfxMessageBox(IXTRE_FILE_MOVE, MB_ICONERROR);
    return;
  }

  const auto silence = XTROptions::App().GuardSilence();
  const auto mfcPath = mfc::ToMFC(newPath.u8string());
  if (auto* newDoc = ExteorApplication::Docs().OpenRSFormDoc(mfcPath, noView);
      newDoc != nullptr) {
    if (sources.ConnectPict2Src(active, *newDoc)) {
      newDoc->OnSaveDocument(mfcPath);
      ExteorApplication::Docs().ShowDocument(*newDoc);
    } else {
      ::AfxMessageBox(IXTRE_OSS_ASSOSIATION, MB_ICONERROR);
      newDoc->OnCloseDocument();
    }
  }
  document.UpdateAllViews(nullptr);
}

void EditOSSFilename(OSSDoc& document) {
  const auto oldPath = std::filesystem::path(mfc::ToUTF8(document.GetPathName()));
  const auto oldName = oldPath.filename();
  dialog::EditString editDlg{ mfc::ToMFC(oldPath.filename().replace_extension("").u8string()) };
  if (editDlg.DoModal() != IDOK) {
    return;
  }
  auto newName = std::filesystem::path(mfc::ToUTF8(editDlg.resultStr)).filename();
  newName.replace_extension(mfc::ToUTF8(ExtensionFor(FileType::oss)));
  if (newName == oldName) {
    return;
  }
  const auto newPath = std::filesystem::path(document.OSS().Src().ossDomain) / newName;

  const auto newMfcPath = mfc::ToMFC(newPath.u8string());
  const auto oldMfcPath = mfc::ToMFC(oldPath.u8string());
  document.OnSaveDocument(oldMfcPath);
  g_Exteor.RemoveMRUItem(oldMfcPath);

  std::filesystem::rename(oldPath, newPath);

  document.SetPathName(newMfcPath);
  document.UpdateAllViews(nullptr);
}

void EditComment(OSSDoc& document) {
  dialog::EditComment editDlg(mfc::ToMFC(document.OSS().comment), mfc::LoadSID(IXTRS_COMMENT));
  if (editDlg.DoModal() == IDOK) {
    document.OSS().comment = mfc::ToSTL(editDlg.comment);
    document.UpdateAllViews(nullptr);
  }
}

void RerunAllOperations(OSSDoc& document) {
  if (!document.DoFileSave()) {
    return;
  }
  const auto userChoice = ::AfxMessageBox(mfc::LoadSID(IXTRS_OSS_RUNALL_PROMPT),
                                        MB_YESNOCANCEL | MB_ICONQUESTION | MB_DEFBUTTON1);
  if (userChoice == IDCANCEL) {
    return;
  } else if (userChoice == IDYES && !CreateOssArchive(document).has_value()) {
    return;
  }
  document.OSS().Ops().ExecuteAll();
  document.DoFileSave();
  document.UpdateAllViews(nullptr);
  ::AfxMessageBox(IXTRS_OSS_RUNALL_SUCCESS, MB_ICONINFORMATION);
}

void BackupOSS(const OSSDoc& document) {
  if (const auto path = CreateOssArchive(document); path.has_value()) {
    ::AfxMessageBox(mfc::FormatSID(IXTRS_OSS_ARCH_SUCCESS, path.value().GetString()), MB_ICONEXCLAMATION);
    ::ShellExecuteW(nullptr, L"open", path.value(), nullptr, nullptr, SW_SHOW);
  }
}

void StandaloneSynthes(const ccl::ops::Type type) {
  dialog::SynthesUI opSheet(type);
  if (opSheet.DoModal() != IDCANCEL) {
    ccl::ops::BinarySynthes operation(*opSheet.operand1, *opSheet.operand2, opSheet.equations);
    const auto silence = XTROptions::App().GuardSilence();
    if (auto result = operation.Execute(); result == nullptr) {
      ::AfxMessageBox(IXTRE_SYNT, MB_ICONERROR);
    } else {
      auto* newDoc = ExteorApplication::Docs().NewRSFormDoc(*result);
      newDoc->Schema().title = mfc::ToSTL(opSheet.title);
      newDoc->Schema().alias = mfc::ToSTL(opSheet.alias);
      newDoc->UpdateAllViews(nullptr);
    }
  }
}

void ExecuteRelation(const ccl::semantic::RSForm& schema, const EntityUID target) {
  using ccl::ops::OpRelativation;

  const auto basicNames = [&] {
    std::string names{};
    for (const auto entity : ccl::semantic::FilterCst(schema.Core(), CstType::base)) {
      if (entity != target) {
        names = names + ' ' + schema.GetRS(entity).alias + ',';
      }
    }
    return names;
  }();
  dialog::EditString dlg(mfc::ToMFC(basicNames), mfc::LoadSID(IXTRS_LABEL_IGNORE_RELATION));
  if (dlg.DoModal() != IDOK) {
    return;
  }
  const auto cstFromExpr = ccl::rslang::ExtractUGlobals(mfc::ToSTL(dlg.resultStr));
  SetOfEntities ignore{};
  ignore.reserve(size(cstFromExpr));
  for (const auto& name : cstFromExpr) {
    if (auto uid = schema.Core().FindAlias(name); uid.has_value()) {
      ignore.emplace(uid.value());
    }
  }
  if (!OpRelativation::IsCorrectlyDefined(schema, target, ignore)) {
    ::AfxMessageBox(IXTRE_RELATE_RS, MB_ICONERROR);
  } else {
    OpRelativation operation(schema, target, ignore);
    ExteorApplication::Docs().NewRSFormDoc(*operation.Execute());
  }
}

void SpawnModelDocument(const RSForm& schema) {
  const auto silence = XTROptions::App().GuardSilence();
  auto& newDoc = *ExteorApplication::Docs().NewRSModelDoc();
  newDoc.Model().alias = schema.alias;
  newDoc.Model().title = schema.title;
  newDoc.Model().comment = schema.comment;
  for (const auto entity : schema.List()) {
    newDoc.Model().InsertCopy(entity, schema.Core());
  }
  newDoc.SetModifiedFlag(TRUE);
  newDoc.UpdateAllViews(nullptr);
}

void SpawnSchemaDocument(const RSModel& model) {
  const auto silence = XTROptions::App().GuardSilence();
  auto& newDoc = *ExteorApplication::Docs().NewRSFormDoc();
  newDoc.Schema().alias = model.alias;
  newDoc.Schema().title = model.title;
  newDoc.Schema().comment = model.comment;
  for (const auto entity : model.List()) {
    newDoc.Schema().InsertCopy(entity, model.Core());
  }
  newDoc.SetModifiedFlag(TRUE);
  newDoc.UpdateAllViews(nullptr);
}

void FollowHyperlink(const CString address, const CString subaddress) {
  static constexpr auto EXECUTE_OK = 32ULL;
  if (ConfirmURL(address)) {
    if (reinterpret_cast<unsigned long long>(::ShellExecuteW(nullptr, L"open", address, subaddress, nullptr, SW_SHOW)) < EXECUTE_OK) {
      // Note: see ShellExecute docs, return value should be cast to int - error return code
      ::AfxMessageBox(mfc::FormatSID(IXTRE_HYPERLINK_FOLLOW, address.GetString()), MB_ICONERROR);
    }
  }
}

} // namespace xtr::proc