import AppKit
import Foundation
import Darwin

// Self-contained AppKit application. Customer Macs do not need Python, Xcode,
// Homebrew, Terminal, or a downloaded interpreter.
let fm = FileManager.default
let home = fm.homeDirectoryForCurrentUser
let label = "com.kelownafilmstudios.macosscreenshot"
let support = home.appendingPathComponent("Library/Application Support/Kelowna Film Studios/macosscreenshot")
let configURL = support.appendingPathComponent("config.json")
let restoreURL = support.appendingPathComponent("restore-settings.json")
let legacyRestoreURL = home.appendingPathComponent(".macos_screenshot_settings.txt")
let agentURL = home.appendingPathComponent("Library/LaunchAgents/\(label).plist")
let installedApp = home.appendingPathComponent("Applications/Screenshot Clock.app")
let modes = ["watch_24h", "once_24h", "macos_default", "system_24h"]
let managed = ["com.apple.screencapture": ["name", "location", "show-thumbnail"], "NSGlobalDomain": ["AppleICUForce24HourTime"]]

struct ToolError: LocalizedError {
    let message: String
    var errorDescription: String? { message }
}

@discardableResult
func command(_ executable: String, _ args: [String], checked: Bool = true) throws -> String {
    let p = Process(), output = Pipe(), errors = Pipe()
    p.executableURL = URL(fileURLWithPath: executable)
    p.arguments = args
    p.standardOutput = output
    p.standardError = errors
    try p.run()
    let text = String(decoding: output.fileHandleForReading.readDataToEndOfFile(), as: UTF8.self)
    let error = String(decoding: errors.fileHandleForReading.readDataToEndOfFile(), as: UTF8.self)
    p.waitUntilExit()
    if checked && p.terminationStatus != 0 { throw ToolError(message: "\(URL(fileURLWithPath: executable).lastPathComponent) failed: \(error.trimmingCharacters(in: .whitespacesAndNewlines))") }
    return text.trimmingCharacters(in: .newlines)
}

func preference(_ domain: String, _ key: String) -> [String: Any] {
    if let value = try? command("/usr/bin/defaults", ["read", domain, key]) {
        return ["exists": true, "value": value]
    }
    return ["exists": false, "value": NSNull()]
}

func writePreference(_ domain: String, _ key: String, _ value: Any?) throws {
    guard let value = value, !(value is NSNull) else {
        _ = try command("/usr/bin/defaults", ["delete", domain, key], checked: false)
        return
    }
    let text = String(describing: value)
    if key == "show-thumbnail" || key == "AppleICUForce24HourTime" {
        guard ["true", "false", "1", "0", "yes", "no"].contains(text.lowercased()) else { throw ToolError(message: "The saved setting for \(key) is invalid. Your restore file has been preserved.") }
        try command("/usr/bin/defaults", ["write", domain, key, "-bool", ["true", "1", "yes"].contains(text.lowercased()) ? "true" : "false"])
    } else {
        try command("/usr/bin/defaults", ["write", domain, key, "-string", text])
    }
}

func json(_ url: URL) throws -> [String: Any] {
    guard let dictionary = try JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any] else { throw ToolError(message: "Cannot read \(url.lastPathComponent). The file has been preserved.") }
    return dictionary
}

func saveJSON(_ dictionary: [String: Any], _ url: URL) throws {
    try fm.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
    try JSONSerialization.data(withJSONObject: dictionary, options: [.prettyPrinted, .sortedKeys]).write(to: url, options: .atomic)
    try fm.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
}

func snapshot() -> [String: Any] {
    var values: [String: Any] = [:]
    for (domain, keys) in managed {
        values[domain] = Dictionary(uniqueKeysWithValues: keys.map { ($0, preference(domain, $0)) })
    }
    return ["version": 1, "app": "Screenshot Clock", "created_at": ISO8601DateFormatter().string(from: Date()), "managed_defaults": values]
}

// Validate the entire restore plan before writing any preference. Accept the
// original Python installer's JSON snapshots, without executing Python.
func restorePlan(_ source: [String: Any]?) throws -> [(String, String, Any?)] {
    var result: [(String, String, Any?)] = []
    var values: [String: Any] = [:]
    if let source = source {
        guard let saved = source["managed_defaults"] as? [String: Any] else { throw ToolError(message: "The saved restore file is incomplete. No settings were changed.") }
        values = saved
    }
    for (domain, keys) in managed {
        for key in keys {
            if source == nil { result.append((domain, key, nil)); continue }
            guard let entries = values[domain] as? [String: Any] else { throw ToolError(message: "The saved restore file is incomplete. No settings were changed.") }
            // Old snapshots predate the managed thumbnail preference.
            if key == "show-thumbnail", entries[key] == nil { continue }
            guard let entry = entries[key] as? [String: Any], let exists = entry["exists"] as? Bool else { throw ToolError(message: "The saved restore file is incomplete. No settings were changed.") }
            let value: Any? = exists ? entry["value"] : nil
            if exists {
                guard let value = value, !(value is NSNull) else { throw ToolError(message: "The saved setting for \(key) is missing. No settings were changed.") }
                if key == "show-thumbnail" || key == "AppleICUForce24HourTime" {
                    guard ["true", "false", "1", "0", "yes", "no"].contains(String(describing: value).lowercased()) else { throw ToolError(message: "Invalid saved \(key). No settings were changed.") }
                } else if !(value is String) { throw ToolError(message: "Invalid saved \(key). No settings were changed.") }
            }
            result.append((domain, key, value))
        }
    }
    return result
}

func savedRestore() throws -> [String: Any]? {
    for url in [restoreURL, legacyRestoreURL] where fm.fileExists(atPath: url.path) { return try json(url) }
    return nil
}

func stopWatcher() throws {
    try command("/bin/launchctl", ["bootout", "gui/\(getuid())/\(label)"], checked: false)
}

func restartScreenshotUI() {
    _ = try? command("/usr/bin/killall", ["SystemUIServer"], checked: false)
}

func convertedName(_ filename: String, prefix: String) throws -> String? {
    let pattern = "^(" + NSRegularExpression.escapedPattern(for: prefix) + " \\d{4}-\\d{2}-\\d{2} at )(\\d{1,2})\\.(\\d{2})\\.(\\d{2})[\\s\\u00a0\\u202f]*(AM|PM)(?: (\\d+)| \\((\\d+)\\))?\\.(png|jpg|jpeg|heic|tif|tiff|gif|pdf)$"
    let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
    let ns = filename as NSString
    guard let match = regex.firstMatch(in: filename, range: NSRange(location: 0, length: ns.length)) else { return nil }
    func group(_ index: Int) -> String { let range = match.range(at: index); return range.location == NSNotFound ? "" : ns.substring(with: range) }
    guard let hour = Int(group(2)), (1...12).contains(hour), let minute = Int(group(3)), minute < 60, let second = Int(group(4)), second < 60 else { return nil }
    let h = hour % 12 + (group(5).uppercased() == "PM" ? 12 : 0)
    let duplicate = Int(group(6).isEmpty ? group(7) : group(6))
    let suffix = duplicate.map { String(format: "-%02d", $0) } ?? ""
    return group(1) + String(format: "%02d.%02d.%02d", h, minute, second) + suffix + "." + group(8).lowercased()
}

@discardableResult
func renameScreenshots(_ folder: URL, prefix: String, minimumAge: TimeInterval = 2) throws -> Int {
    let files = try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isRegularFileKey, .isSymbolicLinkKey, .contentModificationDateKey, .fileSizeKey], options: [.skipsHiddenFiles])
    var count = 0
    for source in files.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) {
        let resource = try source.resourceValues(forKeys: [.isRegularFileKey, .isSymbolicLinkKey, .contentModificationDateKey, .fileSizeKey])
        guard resource.isRegularFile == true, resource.isSymbolicLink != true, (resource.fileSize ?? 0) > 0,
              Date().timeIntervalSince(resource.contentModificationDate ?? Date()) >= minimumAge,
              let targetName = try convertedName(source.lastPathComponent, prefix: prefix) else { continue }
        let target = folder.appendingPathComponent(targetName)
        var destination = target
        if fm.fileExists(atPath: destination.path) {
            var available = false
            for index in 2...999 {
                destination = folder.appendingPathComponent(target.deletingPathExtension().lastPathComponent + String(format: "-%02d", index)).appendingPathExtension(target.pathExtension)
                if !fm.fileExists(atPath: destination.path) { available = true; break }
            }
            if !available { throw ToolError(message: "No free filename for \(source.lastPathComponent). The original was preserved.") }
        }
        // moveItem refuses to overwrite another file; no copying/recompression.
        try fm.moveItem(at: source, to: destination)
        count += 1
    }
    return count
}

func withRenameLock<T>(_ body: () throws -> T) throws -> T {
    try fm.createDirectory(at: support, withIntermediateDirectories: true)
    let fd = Darwin.open(support.appendingPathComponent("rename.lock").path, O_CREAT | O_RDWR | O_NOFOLLOW, 0o600)
    guard fd >= 0 else { throw ToolError(message: "Cannot open the screenshot rename lock.") }
    defer { Darwin.close(fd) }
    guard flock(fd, LOCK_EX | LOCK_NB) == 0 else { throw ToolError(message: "Screenshot Clock is already renaming files. Try again in a moment.") }
    defer { flock(fd, LOCK_UN) }
    return try body()
}

func watcher() throws {
    let config = try json(configURL)
    guard config["mode"] as? String == "watch_24h", let prefix = config["name"] as? String, let path = config["location"] as? String else { return }
    try withRenameLock {
        for _ in 0..<4 {
            _ = try renameScreenshots(URL(fileURLWithPath: path), prefix: prefix)
            Thread.sleep(forTimeInterval: 1)
        }
    }
}

func apply(name: String, folder: URL, mode: String, hidePreview: Bool) throws -> Int {
    guard !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, !name.contains("/"), !name.contains("\n"), modes.contains(mode) else { throw ToolError(message: "Choose a screenshot name without a slash or line break.") }
    // Prompt for folder access in the foreground before changing anything.
    _ = try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil)
    if fm.fileExists(atPath: restoreURL.path) || fm.fileExists(atPath: legacyRestoreURL.path) { _ = try restorePlan(savedRestore()) }
    let current = Bundle.main.bundleURL
    guard current.pathExtension == "app" else { throw ToolError(message: "Open Screenshot Clock.app to set up the utility.") }
    try fm.createDirectory(at: installedApp.deletingLastPathComponent(), withIntermediateDirectories: true)
    // Stage the intact signed bundle. Do not rewrite its contents after signing.
    if current.standardizedFileURL != installedApp.standardizedFileURL {
        let staged = installedApp.deletingLastPathComponent().appendingPathComponent(".Screenshot-Clock-\(UUID().uuidString).app")
        try fm.copyItem(at: current, to: staged)
        defer { try? fm.removeItem(at: staged) }
        if fm.fileExists(atPath: installedApp.path) {
            _ = try fm.replaceItemAt(installedApp, withItemAt: staged)
        } else { try fm.moveItem(at: staged, to: installedApp) }
    }
    if !fm.fileExists(atPath: restoreURL.path) && !fm.fileExists(atPath: legacyRestoreURL.path) {
        // Never label already-managed legacy settings as the original defaults.
        if !fm.fileExists(atPath: configURL.path) && !fm.fileExists(atPath: agentURL.path) { try saveJSON(snapshot(), restoreURL) }
    }
    try stopWatcher()
    let previous = snapshot()
    let oldConfig = try? Data(contentsOf: configURL)
    let oldAgent = try? Data(contentsOf: agentURL)
    do {
        try writePreference("com.apple.screencapture", "name", name)
        try writePreference("com.apple.screencapture", "location", folder.path)
        try writePreference("com.apple.screencapture", "show-thumbnail", hidePreview ? "false" : "true")
        if mode == "system_24h" { try writePreference("NSGlobalDomain", "AppleICUForce24HourTime", "true") }
        try saveJSON(["name": name, "location": folder.path, "mode": mode, "disable_preview_thumbnail": hidePreview, "version": "2.0.0"], configURL)
        var count = 0
        if mode == "watch_24h" || mode == "once_24h" { count = try withRenameLock { try renameScreenshots(folder, prefix: name) } }
        if mode == "watch_24h" {
            let logs = home.appendingPathComponent("Library/Logs/Kelowna Film Studios/macosscreenshot")
            try fm.createDirectory(at: logs, withIntermediateDirectories: true)
            try fm.createDirectory(at: agentURL.deletingLastPathComponent(), withIntermediateDirectories: true)
            let agent: [String: Any] = ["Label": label, "ProgramArguments": [installedApp.appendingPathComponent("Contents/MacOS/ScreenshotClock").path, "--watcher"], "RunAtLoad": true, "WatchPaths": [folder.path], "StartInterval": 15, "ProcessType": "Background", "StandardOutPath": logs.appendingPathComponent("watcher.log").path, "StandardErrorPath": logs.appendingPathComponent("watcher-error.log").path]
            try PropertyListSerialization.data(fromPropertyList: agent, format: .xml, options: 0).write(to: agentURL, options: .atomic)
            try command("/bin/launchctl", ["enable", "gui/\(getuid())/\(label)"])
            try command("/bin/launchctl", ["bootstrap", "gui/\(getuid())", agentURL.path])
            try command("/bin/launchctl", ["print", "gui/\(getuid())/\(label)"])
        } else if fm.fileExists(atPath: agentURL.path) { try fm.removeItem(at: agentURL) }
        restartScreenshotUI()
        return count
    } catch {
        try? stopWatcher()
        for (domain, key, value) in try restorePlan(previous) { try? writePreference(domain, key, value) }
        if let bytes = oldConfig { try? bytes.write(to: configURL, options: .atomic) } else { try? fm.removeItem(at: configURL) }
        if let bytes = oldAgent {
            try? bytes.write(to: agentURL, options: .atomic)
            _ = try? command("/bin/launchctl", ["bootstrap", "gui/\(getuid())", agentURL.path])
        } else { try? fm.removeItem(at: agentURL) }
        restartScreenshotUI()
        throw error
    }
}

func uninstall() throws {
    guard fm.fileExists(atPath: configURL.path) || fm.fileExists(atPath: agentURL.path) || fm.fileExists(atPath: restoreURL.path) || fm.fileExists(atPath: legacyRestoreURL.path) || fm.fileExists(atPath: support.appendingPathComponent("macosscreenshot.py").path) else {
        throw ToolError(message: "Screenshot Clock is not installed. Your screenshot settings have been left unchanged.")
    }
    let plan = try restorePlan(savedRestore())
    try stopWatcher()
    for (domain, key, value) in plan { try writePreference(domain, key, value) }
    for url in [agentURL, legacyRestoreURL, home.appendingPathComponent(".local/bin/macosscreenshot"), home.appendingPathComponent(".local/bin/macosscreenshot-uninstall")] where fm.fileExists(atPath: url.path) { try fm.removeItem(at: url) }
    if fm.fileExists(atPath: support.path) { try fm.removeItem(at: support) }
    restartScreenshotUI()
    // The app remains available until the user moves it to Trash. Never delete
    // screenshot folders, screenshots, or another screenshot automation.
}

func selfTest() throws {
    let folder = fm.temporaryDirectory.appendingPathComponent("ScreenshotClock-tests-\(UUID().uuidString)")
    try fm.createDirectory(at: folder, withIntermediateDirectories: true)
    defer { try? fm.removeItem(at: folder) }
    let cases = ["Screen Shot 2026-06-15 at 11.30.12\u{202f}PM.png": "Screen Shot 2026-06-15 at 23.30.12.png", "Screen Shot 2026-06-15 at 12.00.01 AM.png": "Screen Shot 2026-06-15 at 00.00.01.png", "Screen Shot 2026-06-15 at 12.00.01 PM.png": "Screen Shot 2026-06-15 at 12.00.01.png", "Screen Shot 2026-06-15 at 1.02.03\u{00a0}AM (2).PNG": "Screen Shot 2026-06-15 at 01.02.03-02.png"]
    for (source, _) in cases { try Data("original image bytes".utf8).write(to: folder.appendingPathComponent(source)) }
    let collision = folder.appendingPathComponent("Screen Shot 2026-06-15 at 23.30.12.png")
    try Data("existing file".utf8).write(to: collision)
    try fm.createSymbolicLink(at: folder.appendingPathComponent("Screen Shot 2026-06-15 at 5.00.00 PM.png"), withDestinationURL: collision)
    guard try renameScreenshots(folder, prefix: "Screen Shot", minimumAge: 0) == cases.count else { throw ToolError(message: "Conversion count failed") }
    for target in cases.values where target != collision.lastPathComponent { guard fm.fileExists(atPath: folder.appendingPathComponent(target).path) else { throw ToolError(message: "Missing \(target)") } }
    guard try String(contentsOf: collision, encoding: .utf8) == "existing file", try String(contentsOf: folder.appendingPathComponent("Screen Shot 2026-06-15 at 23.30.12-02.png"), encoding: .utf8) == "original image bytes" else { throw ToolError(message: "Collision preservation failed") }
    guard try renameScreenshots(folder, prefix: "Screen Shot", minimumAge: 0) == 0 else { throw ToolError(message: "Idempotence failed") }
    guard try convertedName("Other 2026-06-15 at 1.02.03 PM.png", prefix: "Screen Shot") == nil, try convertedName("Screen Shot 2026-06-15 at 19.02.03 PM.png", prefix: "Screen Shot") == nil else { throw ToolError(message: "Non-screenshot protection failed") }
    let fresh = folder.appendingPathComponent("Screen Shot 2026-06-15 at 4.00.00 PM.png")
    try Data("unfinished".utf8).write(to: fresh)
    guard try renameScreenshots(folder, prefix: "Screen Shot", minimumAge: 60) == 0 else { throw ToolError(message: "Active capture protection failed") }
    let escapedPrefix = "Shot [studio].+"
    guard try convertedName("\(escapedPrefix) 2026-06-15 at 1.02.03 PM.png", prefix: escapedPrefix) == "\(escapedPrefix) 2026-06-15 at 13.02.03.png" else { throw ToolError(message: "Literal prefix failed") }
    let saved: [String: Any] = ["managed_defaults": ["com.apple.screencapture": ["name": ["exists": true, "value": "Old prefix"], "location": ["exists": true, "value": "/tmp/old location"], "show-thumbnail": ["exists": true, "value": "0"]], "NSGlobalDomain": ["AppleICUForce24HourTime": ["exists": false, "value": NSNull()]]]]
    guard try restorePlan(saved).count == 4, try restorePlan(nil).count == 4 else { throw ToolError(message: "Legacy restoration failed") }
    var rejected = false
    do { _ = try restorePlan(["managed_defaults": [:]]) } catch { rejected = true }
    guard rejected else { throw ToolError(message: "Corrupt snapshot protection failed") }
    print("PASS: midnight/noon, Unicode spaces, duplicates, collisions/bytes, symlinks, idempotence, active captures, literal prefixes, legacy restore and corrupt snapshot protection. No user preferences changed.")
}

final class ClockUI: NSObject, NSApplicationDelegate {
    var window: NSWindow!
    let name = NSTextField(string: "Screen Shot")
    let folderLabel = NSTextField(labelWithString: "")
    let mode = NSPopUpButton(frame: .zero, pullsDown: false)
    let thumbnail = NSButton(checkboxWithTitle: "Keep the floating screenshot preview", target: nil, action: nil)
    let status = NSTextField(wrappingLabelWithString: "Your Mac clock stays unchanged in the recommended mode.")
    var folder = home.appendingPathComponent("Desktop")

    func applicationDidFinishLaunching(_ notification: Notification) {
        NSApp.setActivationPolicy(.regular)
        let menus = NSMenu(), appMenu = NSMenu(), editMenu = NSMenu(title: "Edit")
        let appItem = NSMenuItem(), editItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
        appItem.submenu = appMenu; editItem.submenu = editMenu
        let quit = NSMenuItem(title: "Quit Screenshot Clock", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
        quit.target = NSApp; appMenu.addItem(quit)
        for (title, action, key) in [("Cut", "cut:", "x"), ("Copy", "copy:", "c"), ("Paste", "paste:", "v"), ("Select All", "selectAll:", "a")] {
            editMenu.addItem(NSMenuItem(title: title, action: Selector(action), keyEquivalent: key))
        }
        menus.addItem(appItem); menus.addItem(editItem); NSApp.mainMenu = menus
        window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 580, height: 490), styleMask: [.titled, .closable, .miniaturizable], backing: .buffered, defer: false)
        window.title = "Screenshot Clock"
        window.isReleasedWhenClosed = false
        let stack = NSStackView()
        stack.orientation = .vertical; stack.alignment = .leading; stack.spacing = 14
        stack.translatesAutoresizingMaskIntoConstraints = false
        window.contentView!.addSubview(stack)
        NSLayoutConstraint.activate([stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor, constant: 28), stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor, constant: -28), stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor, constant: 26)])
        let title = NSTextField(labelWithString: "Screenshots in the right order.")
        title.font = .boldSystemFont(ofSize: 25)
        stack.addArrangedSubview(title)
        let intro = NSTextField(wrappingLabelWithString: "Choose your screenshot settings. Screenshot Clock runs locally and saves your original settings so you can restore them later.")
        stack.addArrangedSubview(intro)
        stack.addArrangedSubview(NSTextField(labelWithString: "Screenshot name"))
        name.placeholderString = "Screen Shot"
        stack.addArrangedSubview(name)
        stack.addArrangedSubview(NSTextField(labelWithString: "Save screenshots to"))
        let choose = NSButton(title: "Choose folder…", target: self, action: #selector(chooseFolder))
        let folderRow = NSStackView(views: [folderLabel, choose]); folderRow.spacing = 12
        folderLabel.lineBreakMode = .byTruncatingMiddle
        stack.addArrangedSubview(folderRow)
        mode.addItems(withTitles: ["24-hour filenames · keep Mac clock unchanged", "Convert existing screenshots once", "Keep macOS default naming", "24-hour clock everywhere · affects all apps"])
        stack.addArrangedSubview(mode)
        thumbnail.state = .on; stack.addArrangedSubview(thumbnail)
        let save = NSButton(title: "Save settings", target: self, action: #selector(saveSettings)); save.bezelStyle = .rounded; save.keyEquivalent = "\r"
        let remove = NSButton(title: "Restore settings & uninstall…", target: self, action: #selector(removeSetup)); remove.bezelStyle = .rounded
        let actions = NSStackView(views: [save, remove]); actions.spacing = 10; stack.addArrangedSubview(actions)
        status.textColor = .secondaryLabelColor; stack.addArrangedSubview(status)
        for view in [intro, name, folderRow, mode, status] { view.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true }
        if !CommandLine.arguments.contains("--ui-preview") {
            let config = try? json(configURL)
            name.stringValue = config?["name"] as? String ?? preference("com.apple.screencapture", "name")["value"] as? String ?? "Screen Shot"
            if let location = config?["location"] as? String ?? preference("com.apple.screencapture", "location")["value"] as? String { folder = URL(fileURLWithPath: (location as NSString).expandingTildeInPath) }
            mode.selectItem(at: modes.firstIndex(of: config?["mode"] as? String ?? "watch_24h") ?? 0)
            thumbnail.state = (config?["disable_preview_thumbnail"] as? Bool ?? false) ? .off : .on
        }
        folderLabel.stringValue = folder.path
        if CommandLine.arguments.contains("--ui-preview") { save.isEnabled = false; remove.isEnabled = false; choose.isEnabled = false; status.stringValue = "Review mode. Saving and uninstalling are disabled." }
        window.center(); window.makeKeyAndOrderFront(nil); NSApp.activate(ignoringOtherApps: true)
    }
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }
    @objc func chooseFolder() {
        let panel = NSOpenPanel(); panel.canChooseDirectories = true; panel.canChooseFiles = false; panel.allowsMultipleSelection = false; panel.canCreateDirectories = true; panel.directoryURL = folder
        if panel.runModal() == .OK, let selected = panel.url { folder = selected; folderLabel.stringValue = folder.path }
    }
    func alert(_ title: String, _ message: String, confirm: Bool = false) -> Bool {
        let a = NSAlert(); a.messageText = title; a.informativeText = message; a.addButton(withTitle: confirm ? "Continue" : "OK")
        if confirm { a.addButton(withTitle: "Cancel") }
        return a.runModal() == .alertFirstButtonReturn
    }
    @objc func saveSettings() {
        if mode.indexOfSelectedItem == 3 && !alert("Change the Mac clock everywhere?", "This option changes the time format in all apps. Choose the first mode to change filenames only.", confirm: true) { return }
        do {
            let count = try apply(name: name.stringValue, folder: folder, mode: modes[mode.indexOfSelectedItem], hidePreview: thumbnail.state == .off)
            status.stringValue = "Saved. \(count) existing screenshot(s) renamed. Reopen Screenshot Clock in your Applications folder to change or remove this setup."
        } catch { _ = alert("Settings could not be saved", error.localizedDescription) }
    }
    @objc func removeSetup() {
        guard alert("Restore settings and uninstall?", "This stops Screenshot Clock and restores your saved screenshot settings. Your screenshots and screenshot folders are kept. You can then move the app to Trash.", confirm: true) else { return }
        do { try uninstall(); status.stringValue = "Uninstalled. Your screenshots are kept. You can move Screenshot Clock to Trash." }
        catch { _ = alert("Uninstall could not finish", error.localizedDescription) }
    }
}

if CommandLine.arguments.contains("--self-test") {
    do { try selfTest(); exit(0) } catch { fputs("\(error.localizedDescription)\n", stderr); exit(1) }
} else if CommandLine.arguments.contains("--watcher") {
    do { try watcher(); exit(0) } catch { fputs("\(error.localizedDescription)\n", stderr); exit(1) }
} else {
    let delegate = ClockUI()
    NSApplication.shared.delegate = delegate
    NSApplication.shared.run()
}
