<commit_before>//
//  Copyright © 2017 SwiftCoders. All rights reserved.
//

import UIKit

// TODO: - Rename to SessionFlow

public final class CopyFlow {

    private(set) lazy var rootViewController: UINavigationController = {
        let navigationController = UINavigationController(rootViewController: inputView)
        navigationController.navigationBar.prefersLargeTitles = true
        return navigationController
    }()

    private let pasteboard: UIPasteboard = .general
    private let context: DataContext<[Item]> = AppContext.shared.itemsContext

    private(set) var items: [Item] = [] {
        didSet {
            context.save(items)
            inputView.viewModel = TableViewModel(items: items)
        }
    }

    lazy var inputView: TableViewController = .init(viewModel: .init(items: items))

    public init() {
        items = context.data
    }

    #if DEBUG
    private lazy var debugFlow: DebugFlow = .init()
    #endif
    
    public func start(with parent: UIViewController, reason: AppFlow.Launch.Reason = .normal) {
        parent.add(rootViewController)

        inputView.onDidTapAddBarButtonItem { [weak self] _ in
            self?.presentEditItemViewController()
        }

        inputView.onDidSelectRow { [weak self] indexPath, tableView in
            tableView.deselectRow(at: indexPath, animated: true)

            guard let `self` = self else {
                return
            }

            guard !self.items.isEmpty else {
                self.presentEditItemViewController()
                return
            }

            // Increment copy count
            let row = indexPath.row
            let item = self.items[row]
            let newItem = Item(body: item.body, copyCount: item.copyCount + 1, title: item.title)
            self.items.remove(at: row)
            self.items.insert(newItem, at: row)

            // Copy body to pasteboard
            self.pasteboard.string = newItem.body
        }

        inputView.onDidTapAccessoryButtonForRow { [weak self] indexPath, _ in
            guard let `self` = self, let item = self.items.element(at: indexPath.row) else {
                return
            }

            self.presentEditItemViewController(action: .editing(item, indexPath))
        }

        inputView.onDidCommitEditing { [weak self] edit, indexPath, _ in
            if case .delete = edit {
                self?.items.remove(at: indexPath.row)
            }
        }
        
        #if DEBUG
        inputView.onDebugDidTap { [weak self] in
            guard let `self` = self else {
                return
            }
            self.debugFlow.start(presenter: self.rootViewController)
        }
        #endif

        switch reason {
        case .normal:
            break
        case let .shortcut(item):
            performAction(for: item)
        }
    }

    public func performAction(for shortcutItem: ShortcutItem) {
        switch shortcutItem {
        case let .newItem(completion):
            presentEditItemViewController(completion: completion)
        }
    }

    private func presentEditItemViewController(action: Action = .adding,
                                               completion: ((Bool) -> Void)? = nil) {
        let viewModel = EditViewModel(title: action.item.title, body: action.item.body)
        let inputView = EditViewController(viewModel: viewModel)
        inputView.title = action.title

        let navigationController = UINavigationController(rootViewController: inputView)
        navigationController.navigationBar.prefersLargeTitles = true

        inputView.onDidTapCancelWhileEditing { [weak self] in
            self?.rootViewController.dismiss(animated: true, completion: nil)
        }

        inputView.onDidTapSaveWhileEditing { [weak self] editedItem in
            defer {
                self?.rootViewController.dismiss(animated: true)
            }

            guard let body = editedItem.body, !body.isEmpty else {
                return
            }

            guard case let .editing(existingItem, indexPath) = action else {
                self?.items.append(Item(body: body, copyCount: 0, title: editedItem.title))
                return
            }

            self?.items[indexPath.row] = Item(body: body, copyCount: existingItem.copyCount, title: editedItem.title)
        }

        rootViewController.present(navigationController, animated: true) {
            completion?(true)
        }
    }

    public enum Action {
        case adding
        case editing(Item, IndexPath)
        
        public var title: String {
            switch self {
            case .adding:
                return "Add Item"
            case .editing:
                return "Edit Item"
            }
        }
        
        public var item: Item {
            switch self {
            case .adding:
                return Item()
            case .editing(let item, _):
                return item
            }
        }
    }
}<commit_msg>Add urls extension to Item<commit_after>//
//  Copyright © 2017 SwiftCoders. All rights reserved.
//

import UIKit

// TODO: - Rename to SessionFlow

public final class CopyFlow {

    private(set) lazy var rootViewController: UINavigationController = {
        let navigationController = UINavigationController(rootViewController: inputView)
        navigationController.navigationBar.prefersLargeTitles = true
        return navigationController
    }()

    private let pasteboard: UIPasteboard = .general
    private let context: DataContext<[Item]> = AppContext.shared.itemsContext

    private(set) var items: [Item] = [] {
        didSet {
            context.save(items)
            inputView.viewModel = TableViewModel(items: items)
        }
    }

    lazy var inputView: TableViewController = .init(viewModel: .init(items: items))

    public init() {
        items = context.data
    }

    #if DEBUG
    private lazy var debugFlow: DebugFlow = .init()
    #endif
    
    public func start(with parent: UIViewController, reason: AppFlow.Launch.Reason = .normal) {
        parent.add(rootViewController)

        inputView.onDidTapAddBarButtonItem { [weak self] _ in
            self?.presentEditItemViewController()
        }

        inputView.onDidSelectRow { [weak self] indexPath, tableView in
            tableView.deselectRow(at: indexPath, animated: true)

            guard let `self` = self else {
                return
            }

            guard !self.items.isEmpty else {
                self.presentEditItemViewController()
                return
            }

            // Increment copy count
            let row = indexPath.row
            let item = self.items[row]
            let newItem = Item(body: item.body, copyCount: item.copyCount + 1, title: item.title)
            self.items.remove(at: row)
            self.items.insert(newItem, at: row)

            // Copy body to pasteboard
            self.pasteboard.string = newItem.body
        }

        inputView.onDidTapAccessoryButtonForRow { [weak self] indexPath, _ in
            guard let `self` = self, let item = self.items.element(at: indexPath.row) else {
                return
            }

            self.presentEditItemViewController(action: .editing(item, indexPath))
        }

        inputView.onDidCommitEditing { [weak self] edit, indexPath, _ in
            if case .delete = edit {
                self?.items.remove(at: indexPath.row)
            }
        }
        
        #if DEBUG
        inputView.onDebugDidTap { [weak self] in
            guard let `self` = self else {
                return
            }
            self.debugFlow.start(presenter: self.rootViewController)
        }
        #endif

        switch reason {
        case .normal:
            break
        case let .shortcut(item):
            performAction(for: item)
        }
    }

    public func performAction(for shortcutItem: ShortcutItem) {
        switch shortcutItem {
        case let .newItem(completion):
            presentEditItemViewController(completion: completion)
        }
    }

    private func presentEditItemViewController(action: Action = .adding,
                                               completion: ((Bool) -> Void)? = nil) {
        let viewModel = EditViewModel(title: action.item.title, body: action.item.body)
        let inputView = EditViewController(viewModel: viewModel)
        inputView.title = action.title

        let navigationController = UINavigationController(rootViewController: inputView)
        navigationController.navigationBar.prefersLargeTitles = true

        inputView.onDidTapCancelWhileEditing { [weak self] in
            self?.rootViewController.dismiss(animated: true, completion: nil)
        }

        inputView.onDidTapSaveWhileEditing { [weak self] editedItem in
            defer {
                self?.rootViewController.dismiss(animated: true)
            }

            guard let body = editedItem.body, !body.isEmpty else {
                return
            }

            guard case let .editing(existingItem, indexPath) = action else {
                self?.items.append(Item(body: body, copyCount: 0, title: editedItem.title))
                return
            }

            self?.items[indexPath.row] = Item(body: body, copyCount: existingItem.copyCount, title: editedItem.title)
        }

        rootViewController.present(navigationController, animated: true) {
            completion?(true)
        }
    }

    public enum Action {
        case adding
        case editing(Item, IndexPath)
        
        public var title: String {
            switch self {
            case .adding:
                return "Add Item"
            case .editing:
                return "Edit Item"
            }
        }
        
        public var item: Item {
            switch self {
            case .adding:
                return Item()
            case .editing(let item, _):
                return item
            }
        }
    }
}

extension Item {
    var urls: [URL] {
        let detector = try? NSDataDetector(types: NSTextCheckingAllTypes)
        let range = NSRange(location: 0, length: body.count)
        let matches = detector?.matches(in: body, options: .init(rawValue: 0), range: range) ?? []
        return matches.compactMap { $0.url }
    }
}
{'fraction_non_alphanumeric': 0.08347826086956522, 'fraction_numerical': 0.0014492753623188406, 'mean_word_length': 1.8021115322144017, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5595714', 'n_tokens_mistral': 2765, 'n_tokens_neox': 2616, 'n_words': 662}