<commit_before>//
//  DeleteFromSidebarCommand.swift
//  NetNewsWire
//
//  Created by <NAME> on 11/4/17.
//  Copyright © 2017 Ranchero Software. All rights reserved.
//

import Foundation
import RSCore
import RSTree
import Account
import Articles

final class DeleteFromSidebarCommand: UndoableCommand {

	let treeController: TreeController
	let undoManager: UndoManager
	let undoActionName: String
	var redoActionName: String {
		return undoActionName
	}

	private let itemSpecifiers: [SidebarItemSpecifier]

	init?(nodesToDelete: [Node], treeController: TreeController, undoManager: UndoManager) {

		guard DeleteFromSidebarCommand.canDelete(nodesToDelete) else {
			return nil
		}
		guard let actionName = DeleteActionName.name(for: nodesToDelete) else {
			return nil
		}

		self.treeController = treeController
		self.undoActionName = actionName
		self.undoManager = undoManager

		let itemSpecifiers = nodesToDelete.compactMap{ SidebarItemSpecifier(node: $0) }
		guard !itemSpecifiers.isEmpty else {
			return nil
		}
		self.itemSpecifiers = itemSpecifiers
	}

	func perform() {

		BatchUpdate.shared.perform {
			itemSpecifiers.forEach { $0.delete() }
			treeController.rebuild()
		}
		registerUndo()
	}

	func undo() {

		BatchUpdate.shared.perform {
			itemSpecifiers.forEach { $0.restore() }
			treeController.rebuild()
		}
		registerRedo()
	}

	static func canDelete(_ nodes: [Node]) -> Bool {

		// Return true if all nodes are feeds and folders.
		// Any other type: return false.

		if nodes.isEmpty {
			return false
		}

		for node in nodes {
			if let _ = node.representedObject as? Feed {
				continue
			}
			if let _ = node.representedObject as? Folder {
				continue
			}
			return false
		}

		return true
	}
}

// Remember as much as we can now about the items being deleted,
// so they can be restored to the correct place.

private struct SidebarItemSpecifier {

	private weak var account: Account?
	private let parentFolder: Folder?
	private let folder: Folder?
	private let feed: Feed?
	private let path: ContainerPath

	private var container: Container? {
		if let parentFolder = parentFolder {
			return parentFolder
		}
		if let account = account {
			return account
		}
		return nil
	}

	init?(node: Node) {

		var account: Account?

		self.parentFolder = node.parentFolder()

		if let feed = node.representedObject as? Feed {
			self.feed = feed
			self.folder = nil
			account = feed.account
		}
		else if let folder = node.representedObject as? Folder {
			self.feed = nil
			self.folder = folder
			account = folder.account
		}
		else {
			return nil
		}
		if account == nil {
			return nil
		}

		self.account = account!
		self.path = ContainerPath(account: account!, folders: node.containingFolders())
	}

	func delete() {

		guard let container = container else {
			return
		}

		if let feed = feed {
			container.deleteFeed(feed)
		}
		else if let folder = folder {
			container.deleteFolder(folder)
		}
	}

	func restore() {

		if let _ = feed {
			restoreFeed()
		}
		else if let _ = folder {
			restoreFolder()
		}
	}

	private func restoreFeed() {

		guard let account = account, let feed = feed else {
			return
		}

		let feedToUse = uniquedFeed(feed)
		account.addFeed(feedToUse, to: resolvedFolder())
	}

	private func restoreFolder() {

		guard let account = account, let folder = folder else {
			return
		}
		account.addFolder(folder, to: nil)
	}

	private func uniquedFeed(_ feed: Feed) -> Feed {

		// A Feed may appear in multiple places in a given account,
		// but it’s best if they’re the same Feed instance.
		// Usually this will return the same Feed that was passed-in,
		// but not necessarily always.

		return account?.existingFeed(with: feed.feedID) ?? feed
	}

	private func resolvedFolder() -> Folder? {

		return path.resolveContainer() as? Folder
	}
}

private extension Node {

	func parentFolder() -> Folder? {

		guard let parentNode = self.parent else {
			return nil
		}
		if parentNode.isRoot {
			return nil
		}<commit_msg>Remove instance of no-longer-needed feed uniquing.<commit_after>//
//  DeleteFromSidebarCommand.swift
//  NetNewsWire
//
//  Created by <NAME> on 11/4/17.
//  Copyright © 2017 Ranchero Software. All rights reserved.
//

import Foundation
import RSCore
import RSTree
import Account
import Articles

final class DeleteFromSidebarCommand: UndoableCommand {

	let treeController: TreeController
	let undoManager: UndoManager
	let undoActionName: String
	var redoActionName: String {
		return undoActionName
	}

	private let itemSpecifiers: [SidebarItemSpecifier]

	init?(nodesToDelete: [Node], treeController: TreeController, undoManager: UndoManager) {

		guard DeleteFromSidebarCommand.canDelete(nodesToDelete) else {
			return nil
		}
		guard let actionName = DeleteActionName.name(for: nodesToDelete) else {
			return nil
		}

		self.treeController = treeController
		self.undoActionName = actionName
		self.undoManager = undoManager

		let itemSpecifiers = nodesToDelete.compactMap{ SidebarItemSpecifier(node: $0) }
		guard !itemSpecifiers.isEmpty else {
			return nil
		}
		self.itemSpecifiers = itemSpecifiers
	}

	func perform() {

		BatchUpdate.shared.perform {
			itemSpecifiers.forEach { $0.delete() }
			treeController.rebuild()
		}
		registerUndo()
	}

	func undo() {

		BatchUpdate.shared.perform {
			itemSpecifiers.forEach { $0.restore() }
			treeController.rebuild()
		}
		registerRedo()
	}

	static func canDelete(_ nodes: [Node]) -> Bool {

		// Return true if all nodes are feeds and folders.
		// Any other type: return false.

		if nodes.isEmpty {
			return false
		}

		for node in nodes {
			if let _ = node.representedObject as? Feed {
				continue
			}
			if let _ = node.representedObject as? Folder {
				continue
			}
			return false
		}

		return true
	}
}

// Remember as much as we can now about the items being deleted,
// so they can be restored to the correct place.

private struct SidebarItemSpecifier {

	private weak var account: Account?
	private let parentFolder: Folder?
	private let folder: Folder?
	private let feed: Feed?
	private let path: ContainerPath

	private var container: Container? {
		if let parentFolder = parentFolder {
			return parentFolder
		}
		if let account = account {
			return account
		}
		return nil
	}

	init?(node: Node) {

		var account: Account?

		self.parentFolder = node.parentFolder()

		if let feed = node.representedObject as? Feed {
			self.feed = feed
			self.folder = nil
			account = feed.account
		}
		else if let folder = node.representedObject as? Folder {
			self.feed = nil
			self.folder = folder
			account = folder.account
		}
		else {
			return nil
		}
		if account == nil {
			return nil
		}

		self.account = account!
		self.path = ContainerPath(account: account!, folders: node.containingFolders())
	}

	func delete() {

		guard let container = container else {
			return
		}

		if let feed = feed {
			container.deleteFeed(feed)
		}
		else if let folder = folder {
			container.deleteFolder(folder)
		}
	}

	func restore() {

		if let _ = feed {
			restoreFeed()
		}
		else if let _ = folder {
			restoreFolder()
		}
	}

	private func restoreFeed() {

		guard let account = account, let feed = feed else {
			return
		}
		account.addFeed(feed, to: resolvedFolder())
	}

	private func restoreFolder() {

		guard let account = account, let folder = folder else {
			return
		}
		account.addFolder(folder, to: nil)
	}

	private func resolvedFolder() -> Folder? {

		return path.resolveContainer() as? Folder
	}
}

private extension Node {

	func parentFolder() -> Folder? {

		guard let parentNode = self.parent else {
			return nil
		}
		if parentNode.isRoot {
			return nil
		}
{'fraction_non_alphanumeric': 0.08896425776154449, 'fraction_numerical': 0.0031307070180015655, 'mean_word_length': 3.2570794003331485, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6705021', 'n_tokens_mistral': 2744, 'n_tokens_neox': 2326, 'n_words': 728}