/// <summary>
        /// Removes an item from collection
        /// </summary>
        /// <param name="topic"></param>
        /// <returns></returns>
        public void DeleteTopic(DocTopic topic)
        {
            if (topic == null)
            {
                return;
            }

            if (topic.Topics.Count > 0)
            {
                var childTopics = topic.Topics.ToList();
                foreach (var childTopic in childTopics)
                {
                    DeleteTopic(childTopic);
                }
            }

            topic.DeleteTopicFile();

            if (topic.Parent?.Topics != null)
            {
                topic.Parent.Topics.Remove(topic);
            }
            else
            {
                Topics.Remove(topic);
            }

            topic = null;
        }
        /// <summary>
        /// Finds a topic in the tree by comparing the link
        /// rather than an object reference. This allows finding
        /// topics that were not in the original project references
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="rootTopics"></param>
        /// <returns></returns>
        public DocTopic FindTopicInTreeByValue(DocTopic topic, ObservableCollection <DocTopic> rootTopics = null)
        {
            if (rootTopics == null)
            {
                rootTopics = Topics;
            }

            if (rootTopics == null)
            {
                return(null);
            }

            foreach (var top in rootTopics)
            {
                if (top.Link == topic.Link)
                {
                    return(top);
                }

                var foundTopic = FindTopicInTree(top, top.Topics);
                if (foundTopic != null)
                {
                    return(foundTopic);
                }
            }

            return(null);
        }
        public DocTopic FindTopicInFlatTree(DocTopic topic, ObservableCollection <DocTopic> rootTopics = null)
        {
            if (rootTopics == null)
            {
                rootTopics = Topics;
            }

            return(rootTopics?.FirstOrDefault(t => t.Id == topic.Id));
        }
        /// <summary>
        /// Updates a topic from a markdown document
        /// </summary>
        /// <param name="doc">document to update from (from disk)</param>
        /// <param name="topic">topic to update</param>
        public void UpdateTopicFromMarkdown(MarkdownDocument doc, DocTopic topic, bool noBody = false)
        {
            var fileTopic = new DocTopic();

            fileTopic.Project     = this;
            fileTopic.DisplayType = string.Empty;
            fileTopic.Type        = null;
            fileTopic.LoadTopicFile(doc.Filename); // make sure we have latest


            if (!string.IsNullOrEmpty(fileTopic.Title))
            {
                topic.Title = fileTopic.Title;
            }

            if (!string.IsNullOrEmpty(fileTopic.Slug))
            {
                topic.Slug = fileTopic.Slug;
            }

            if (!string.IsNullOrEmpty(fileTopic.Link))
            {
                topic.Link = fileTopic.Link;
            }

            // TODO: Figure out how we can detect whether this has changed or is the default
            //       Problem is that DisplayType gets set to a default if n
            if (!string.IsNullOrEmpty(fileTopic.DisplayType))
            {
                topic.DisplayType = fileTopic.DisplayType;
            }


            if (!string.IsNullOrEmpty(fileTopic.Type))
            {
                topic.Type = fileTopic.Type;
            }



            if (!noBody)
            {
                topic.Body = fileTopic.Body;
            }
        }
        /// <summary>
        /// Retrieves a topic but doesn't load it into Active Topic
        /// based on a topic id, slug or html link
        /// </summary>
        /// <param name="topicId"></param>
        /// <returns>topic or null</returns>
        public DocTopic GetTopic(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                return(null);
            }

            DocTopic topic = null;

            if (id.StartsWith("_"))
            {
                string strippedSlugId = id;
                if (!string.IsNullOrEmpty(strippedSlugId) && strippedSlugId.StartsWith("_"))
                {
                    strippedSlugId = id.Substring(1);
                }

                topic = Topics
                        .FirstOrDefault(t => t.Id == id ||
                                        t.Slug == id ||
                                        t.Slug == strippedSlugId);
                return(topic);
            }

            if (id.StartsWith("msdn:"))
            {
                // for now just return as is
                throw new NotSupportedException();
            }
            else if (id.StartsWith("sig:"))
            {
                throw new NotSupportedException();
            }

            // Must be topic title
            topic = Topics
                    .FirstOrDefault(t => t.Title != null && t.Title.ToLower() == id.ToLower() ||
                                    t.Slug != null && t.Slug.ToLower() == id.ToLower());


            return(topic);
        }
        /// <summary>
        /// Recursively fills all topics with subtopics
        /// reorders them and assigns the appropriate
        /// hierarchical ids and dependencies
        /// </summary>
        /// <param name="topic"></param>
        /// <param name="topicList"></param>
        /// <returns></returns>
        public void GetChildTopicsForTopic(DocTopic topic)
        {
            if (topic.Topics == null)
            {
                topic.Topics = new ObservableCollection <DocTopic>();
                return;
            }

            var children = topic.Topics
                           .OrderByDescending(t => t.SortOrder)
                           .ThenBy(t => t.DisplayType)
                           .ThenBy(t => t.Title).ToList();

            foreach (var childTopic in children)
            {
                GetChildTopicsForTopic(childTopic);
                childTopic.Parent   = topic;
                childTopic.ParentId = topic.Id;
                childTopic.Project  = this;
            }
        }
        /// <summary>
        /// Common code that performs after topic loading logic
        /// </summary>
        /// <param name="topic"></param>
        /// <returns></returns>
        protected DocTopic AfterTopicLoaded(DocTopic topic)
        {
            if (topic == null)
            {
                Topic = null;
                SetError("Topic not found.");
            }

            topic.Project = this;

            topic.TopicState.IsDirty = false;
            topic.TopicState.OldLink = null;
            topic.TopicState.OldSlug = null;

            if (!topic.LoadTopicFile()) // load disk content
            {
                SetError("Topic body content not found.");
                return(null);
            }

            return(topic);
        }
        /// <summary>
        /// Creates a new project structure in the ProjectFolder
        /// specified.
        /// </summary>
        /// <returns></returns>
        public DocProject CreateProject()
        {
            if (string.IsNullOrEmpty(ProjectFolder))
            {
                SetError("Please provide a Project Folder.");
                return(null);
            }

            if (string.IsNullOrEmpty(Title))
            {
                SetError("Please provide a Title for the new project.");
                return(null);
            }

            if (!IsTargetFolderMissingOrEmpty(ProjectFolder))
            {
                SetError("Couldn't create new project: Project exists already - please use another folder.");
                return(null);
            }

            string filename = "_toc.json";

            if (string.IsNullOrEmpty(filename))
            {
                filename = Path.Combine(ProjectFolder, "_toc.json");
            }
            else
            {
                filename = Path.Combine(ProjectFolder, filename);
            }

            var project = new DocProject()
            {
                Title    = Title,
                Filename = filename,
                Owner    = Owner
            };

            if (!CopyProjectAssets(project))
            {
                return(null);
            }


            string body  = @"## Welcome to your new Documentation Project

Here are a few tips to get started:

* Press **ctrl-n** to create a new Topic
* Enter text into the main text area using [Markdown formatting](https://documentationmonster.west-wind.com)
* Use the Topic Editor on the right to enter topic data
* Use drag and drop in the Topic tree to move topics around
* Use the Image toolbar icon to select images from disk
* Paste images from from the clipboard into your text

Time to get going!
";
            var    topic = new DocTopic(project)
            {
                Title       = Title,
                Body        = body,
                DisplayType = "index"
            };

            project.Topics.Add(topic);
            project.SaveProject();

            return(project);
        }
        /// <summary>
        /// Recursively fills all topics with subtopics
        /// </summary>
        /// <param name="topics"></param>
        /// <param name="top"></param>
        /// <returns></returns>
        public void GetChildTopicsForTopicFromFlatList(ObservableCollection <DocTopic> topics, DocTopic topic)
        {
            if (topics == null)
            {
                topics = new ObservableCollection <DocTopic>();
            }

            var query = topics.Where(t => t.ParentId == topic.Id);

            if (ProjectSettings.AutoSortTopics)
            {
                query = query
                        .OrderByDescending(t => t.SortOrder)
                        .ThenBy(t => t.DisplayType)
                        .ThenBy(t => t.Title);
            }

            var children = query.ToList();

            if (topic.Topics != null)
            {
                topic.Topics.Clear();
            }
            else
            {
                topic.Topics = new ObservableCollection <DocTopic>();
            }

            foreach (var childTopic in children)
            {
                childTopic.Parent   = topic;
                childTopic.ParentId = topic.Id;
                childTopic.Project  = this;

                string slug     = childTopic.CreateSlug();
                string baseSlug = topic.Slug;
                if (!string.IsNullOrEmpty(baseSlug))
                {
                    baseSlug += "/";
                }

                childTopic.Slug = baseSlug + slug;

                if (childTopic.IsHeaderTopic())
                {
                    childTopic.Link = baseSlug + slug + "/" + slug + ".md";
                }
                else
                {
                    childTopic.Link = baseSlug + slug + ".md";
                }

                topic.Topics.Add(childTopic);

                childTopic.SaveTopicFile();
            }
        }
        /// <summary>
        /// Saves a topic safely into the topic list.
        /// </summary>
        /// <param name="topic"></param>
        /// <returns></returns>
        /// <remarks>
        /// Note: This DOES NOT save the topic to disk tree to disk.
        /// The tree has to be explicitly updated
        /// </remarks>
        public bool SaveTopic(DocTopic topic = null)
        {
            if (topic == null)
            {
                topic = Topic;
            }
            if (topic == null)
            {
                return(false);
            }

            var loadTopic = FindTopicInTree(topic, Topics);

            if (string.IsNullOrEmpty(topic.Title))
            {
                var lines     = StringUtils.GetLines(topic.Body);
                var titleLine = lines.FirstOrDefault(l => l.TrimStart().StartsWith("# "));
                if (!string.IsNullOrEmpty(titleLine) && titleLine.Length > 2)
                {
                    topic.Title = titleLine.Trim().Substring(2);
                }
            }

            if (loadTopic == null)
            {
                using (var updateLock = new TopicFileUpdateLock())
                {
                    if (topic.Parent != null)
                    {
                        topic.Parent.Topics.Add(topic);
                    }
                    else
                    {
                        Topics.Add(topic);
                    }


                    if (topic.SaveTopicFile())
                    {
                        topic.TopicState.IsDirty = false;
                        topic.TopicState.OldLink = null;
                        topic.TopicState.OldSlug = null;
                        return(true);
                    }
                    return(false);
                }
            }

            if (loadTopic.Id == topic.Id)
            {
                var topics = loadTopic.Parent?.Topics;
                if (topic == null)
                {
                    topics = Topics;
                }

                // Replace topic
                for (int i = 0; i < topics.Count; i++)
                {
                    var tpc = topics[i];
                    if (tpc == null)
                    {
                        using (var updateLock = new TopicFileUpdateLock())
                        {
                            topics.RemoveAt(i);
                        }

                        continue;
                    }

                    if (topics[i].Id == topic.Id)
                    {
                        using (var updateLock = new TopicFileUpdateLock())
                        {
                            topics[i] = topic;
                            if (topic.SaveTopicFile())
                            {
                                topic.TopicState.IsDirty = false;
                                topic.TopicState.OldLink = null;
                                topic.TopicState.OldSlug = null;
                                return(true);
                            }
                            return(false);
                        }
                    }
                }
            }

            return(false);
        }
 /// <summary>
 /// Creates a new topic and assigns it to the Topic property.
 /// </summary>
 /// <returns></returns>
 public DocTopic NewTopic()
 {
     Topic = new DocTopic(this);
     return(Topic);
 }