//
//  AppDelegate+Scriptability.swift
//  NetNewsWire
//
//  Created by Olof Hellman on 2/7/18.
//  Copyright © 2018 Olof Hellman. All rights reserved.
//

/*
    Note:  strictly, the AppDelegate doesn't appear as part of the scripting model,
    so this file is rather unlike the other Object+Scriptability.swift files.
    However, the AppDelegate object is the de facto scripting accessor for some
    application elements and properties.  For, example, the main window is accessed
    via the AppDelegate's MainWindowController, and the main window itself has
    selected feeds, selected articles and a current article.  This file supplies the glue to access
    these scriptable objects, while being completely separate from the core AppDelegate code,
*/

import Foundation
import Articles

protocol AppDelegateAppleEvents {
    
	@MainActor func installAppleEventHandlers()
	@MainActor func getURL(_ event: NSAppleEventDescriptor, _ withReplyEvent: NSAppleEventDescriptor)
}

protocol ScriptingAppDelegate {
    
	@MainActor var  scriptingCurrentArticle: Article?  {get}
	@MainActor var  scriptingSelectedArticles: [Article]  {get}
	@MainActor var  scriptingMainWindowController:ScriptingMainWindowController? {get}
}

extension AppDelegate : AppDelegateAppleEvents {
    
    // MARK: GetURL Apple Event

    func installAppleEventHandlers() {
        NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.getURL(_:_:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
    }
    
    @objc func getURL(_ event: NSAppleEventDescriptor, _ withReplyEvent: NSAppleEventDescriptor) {

        guard var urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else {
            return
        }

		// Handle themes
		if urlString.hasPrefix("netnewswire://theme") {
			guard let comps = URLComponents(string: urlString),
				  let queryItems = comps.queryItems,
				  let themeURLString = queryItems.first(where: { $0.name == "url" })?.value else {
					  return
				  }
			
			if let themeURL = URL(string: themeURLString) {
				let request = URLRequest(url: themeURL)
				let task = URLSession.shared.downloadTask(with: request) { location, response, error in
					guard let location = location else {
						return
					}
					
					do {
						try ArticleThemeDownloader.handleFile(at: location)
					} catch {
						NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error])
					}
				}
				task.resume()
			}
			return
		
		}
		
		
		// Special case URL with specific scheme handler x-netnewswire-feed: intended to ensure we open
		// it regardless of which news reader may be set as the default
		let nnwScheme = "x-netnewswire-feed:"
		if urlString.hasPrefix(nnwScheme) {
			urlString = urlString.replacingOccurrences(of: nnwScheme, with: "feed:")
		}

        let normalizedURLString = urlString.normalizedURL
        if !normalizedURLString.mayBeURL {
            return
        }

        DispatchQueue.main.async {

            self.addFeed(normalizedURLString)
        }
    }
}

class NetNewsWireCreateElementCommand : NSCreateCommand {
	@MainActor override func performDefaultImplementation() -> Any? {
         let classDescription = self.createClassDescription
         if (classDescription.className == "feed") {
             return ScriptableFeed.handleCreateElement(command:self)
         } else if (classDescription.className == "folder") {
             return ScriptableFolder.handleCreateElement(command:self)
         }
         return nil
    }
}

/*
    NSDeleteCommand is kind of an oddball AppleScript command in that the command dispatch
    goes to the container of the object(s) to be deleted, and the container needs to
    figure out what to delete. In the code below, 'receivers' is the container object(s)
    and keySpecifier is the thing to delete, relative to the container(s).  Because there
    is ambiguity about whether specifiers are lists or single objects, the code switches
    based on which it is.
*/
class NetNewsWireDeleteCommand : NSDeleteCommand {

    /*
        delete(objectToDelete:, from container:)
        At this point in handling the command, we know what the container is.
        Here the code unravels the case of objectToDelete being a list or a single object,
        ultimately calling container.deleteElement(element) for each element to delete
    */
    func delete(objectToDelete:Any, from container:ScriptingObjectContainer) {
        if let objectList = objectToDelete as? [Any] {
            for nthObject in objectList {
                self.delete(objectToDelete:nthObject, from:container)
            }
        } else if let element = objectToDelete as? ScriptingObject {
            container.deleteElement(element)
        }
    }
    
    /*
        delete(specifier:, from container:)
        At this point in handling the command, the container could be a list or a single object,
        and what to delete is still an unresolved NSScriptObjectSpecifier.
        Here the code unravels the case of container being a list or a single object. Once the
        container(s) is known, it is possible to resolve the keySpecifier based on that container.
        After resolving, we call delete(objectToDelete:, from container:) with the container and
        the resolved objects
    */
    func delete(specifier:NSScriptObjectSpecifier, from container:Any) {
        if let containerList = container as? [Any] {
            for nthObject in containerList {
                self.delete(specifier:specifier, from:nthObject)
            }
        } else if let container = container as? ScriptingObjectContainer {
            if let resolvedObjects = specifier.objectsByEvaluating(withContainers:container) {
                self.delete(objectToDelete:resolvedObjects, from:container)
            }
        }
    }

    /*
        performDefaultImplementation()
        This is where handling the delete event starts. receiversSpecifier should be the container(s) of
        the item to be deleted. keySpecifier is the thing in that container(s) to be deleted
        The first step is to resolve the receiversSpecifier and then call delete(specifier:, from container:)
    */
    override func performDefaultImplementation() -> Any? {
         if let receiversSpecifier = self.receiversSpecifier {
             if let receiverObjects = receiversSpecifier.objectsByEvaluatingSpecifier {
                self.delete(specifier:self.keySpecifier, from:receiverObjects)
             } 
         }
         return nil
    }
}

class NetNewsWireExistsCommand : NSExistsCommand {

    // cocoa default behavior doesn't work here, because of cases where we define an object's property
    // to be another object type.  e.g., 'permalink of the current article' parses as
    //    <property> of <property> of <top level object>
    // cocoa would send the top level object (the app) a doesExist message for a nested property, and
    // it errors out because it doesn't know how to handle that
    // What we do instead is simply see if the defaultImplementation errors, and if it does, the object
    // must not exist.  Otherwise, we return the result of the defaultImplementation
    // The wrinkle is that it is possible that the direct object is a list, so we need to
    // handle that case as well
    
    override func performDefaultImplementation() -> Any? {
         guard let result = super.performDefaultImplementation() else { return NSNumber(booleanLiteral:false) }
         return result
    }
}

