// NTPPlugInController.swift
//
// Principal class for the NTP analyzer plugin. Loaded once at host launch.
// Registers the plugin with the host's CPAPlugInsController and declares its
// settings surface.
//
// Settings are declarative: conforming to `CPAPluginSettingsHosting` and
// returning a color spec, a toggle list and a ports spec is all it takes for
// the analyzer to appear fully configurable under Settings ▸ Analyzers ▸ NTP
// Analyzer. The host draws the switches, the color picker and the editable
// Name/Ports/Source table itself — this plugin builds no settings view.

import AppKit
import SwiftUI
import CPAPluginSDK

@MainActor
final class NTPPlugInController: NSObject, ObservableObject,
    CPAPluginPrincipalClassProtocol,
    CPAPluginSettingsHosting {

    // MARK: - User-defaults keys

    /// One prefix per plugin. Namespace it with your reverse-DNS identifier so
    /// it can never collide with another analyzer's keys.
    ///
    /// `nonisolated` because the analyzer reads these keys off the main thread,
    /// while this controller is `@MainActor`.
    nonisolated private static let defaultsPrefix = "com_example_cpa_ntp"
    nonisolated static let kPortsKey = "\(defaultsPrefix)_protocols"
    nonisolated static let kLocalTimestamps = "\(defaultsPrefix)_local_timestamps"

    // MARK: - Init

    required convenience init?(plugInsController: Any) { self.init() }
    required convenience init?(plugInManager: Any) { self.init() }

    override required convenience init() {
        self.init(controller: CPAPlugInsController.sharedManager)!
    }

    init?(controller: CPAPlugInsController) {
        super.init()
        // Seed every declared key before registering — `setupPlugIn` below reads
        // the stored ports back out.
        setDefaultSettings()
        register(with: controller)
    }

    // MARK: - Plugin metadata (CPAPluginPrincipalClassProtocol)

    static func pluginTitle() -> String? {
        Bundle(for: NTPPlugInController.self).localized("NTP Analyzer")
    }

    static func pluginDescription() -> String? {
        Bundle(for: NTPPlugInController.self).localized("Network Time Protocol v4 Analyzer (RFC 5905)")
    }

    static func pluginVersion() -> String? {
        let bundle = Bundle(for: NTPPlugInController.self)
        let short = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0"
        let build = bundle.infoDictionary?[kCFBundleVersionKey as String] as? String ?? "0"
        return "\(short) (\(build))"
    }

    static func pluginAuthor() -> String? {
        Bundle(for: NTPPlugInController.self).localized("CPA Plugin SDK example")
    }

    // MARK: - Settings (CPAPluginSettingsHosting)

    /// The registration. Port edits in the settings pane mutate and re-register
    /// this, so it must be a stored, settable property.
    @Published var pluginStruct: CPAPlugInStructure?

    /// Adds the "Packet coloring" switch + swatch to the pane.
    var settingsColor: AnalyzerColorSpec? {
        .prefixed(NTPPlugInController.defaultsPrefix)
    }

    /// One plugin-specific switch. `labelKey` is the label as it appears on
    /// screen — keys the host already localizes are translated, anything else is
    /// shown verbatim, so write the sentence you want the user to read.
    var settingsToggles: [AnalyzerToggleSpec] {
        [
            AnalyzerToggleSpec(
                defaultsKey: NTPPlugInController.kLocalTimestamps,
                labelKey: "Show NTP timestamps in local time",
                defaultValue: false
            )
        ]
    }

    /// The editable ports table. One row per port binding; these are the factory
    /// defaults the pane's Reset button restores. NTP is UDP/123 (RFC 5905 §7.2)
    /// and the analyzer also accepts the SNTP port some appliances use, so both
    /// names are offered in the row's Name menu.
    var settingsPorts: AnalyzerPortsSpec? {
        .rows(
            [.udp("NTP", [123])],
            names: ["NTP", "SNTP"],
            defaultsKey: NTPPlugInController.kPortsKey
        )
    }

    // MARK: - Private

    private func register(with controller: CPAPlugInsController) {
        let bundle = Bundle(for: type(of: self))

        let columns: [CPAPlugInColumn] = [
            CPAPlugInColumn.column(
                title: bundle.localized("NTP Size"),
                keyPath: "ntpSize",
                predicateDisplayName: bundle.localized("NTP Size contains"),
                predicateFormat: "ntpSize.description CONTAINS $value",
                attrType: NSString.self
            ),
            CPAPlugInColumn.column(
                title: bundle.localized("NTP Mode"),
                keyPath: "ntpMode",
                predicateDisplayName: bundle.localized("NTP Mode contains"),
                predicateFormat: "ntpMode CONTAINS[c] $value",
                attrType: String.self
            ),
            CPAPlugInColumn.column(
                title: bundle.localized("NTP Stratum"),
                keyPath: "ntpStratum",
                predicateDisplayName: bundle.localized("NTP Stratum contains"),
                predicateFormat: "ntpStratum.description CONTAINS $value",
                attrType: NSString.self
            ),
            CPAPlugInColumn.column(
                title: bundle.localized("NTP Reference"),
                keyPath: "ntpReference",
                predicateDisplayName: bundle.localized("NTP Reference contains"),
                predicateFormat: "ntpReference CONTAINS[c] $value",
                attrType: String.self
            ),
            CPAPlugInColumn.column(
                title: bundle.localized("NTP ProtocolInfo"),
                keyPath: "ntpProtocolInfo",
                predicateDisplayName: bundle.localized("NTP ProtocolInfo contains"),
                predicateFormat: "ntpProtocolInfo CONTAINS[c] $value",
                attrType: String.self
            )
        ]

        let attributes: [CPAPlugInAnalyzerAttribute] = [
            .attribute(class: NTPProtocolAnalyzer.self, title: bundle.localized("NTP Size"), key: "ntpSize", type: NSNumber.self),
            .attribute(class: NTPProtocolAnalyzer.self, title: bundle.localized("NTP Mode"), key: "ntpMode", type: String.self),
            .attribute(class: NTPProtocolAnalyzer.self, title: bundle.localized("NTP Stratum"), key: "ntpStratum", type: NSNumber.self),
            .attribute(class: NTPProtocolAnalyzer.self, title: bundle.localized("NTP Reference"), key: "ntpReference", type: String.self),
            .attribute(class: NTPProtocolAnalyzer.self, title: bundle.localized("NTP ProtocolInfo"), key: "ntpProtocolInfo", type: String.self)
        ]

        // Register the *stored* ports, not the hard-coded ones — otherwise every
        // launch would re-bind the factory ports and throw away the user's edits.
        let plugin = CPAPlugInStructure.register(
            title: NTPPlugInController.pluginTitle() ?? "NTP",
            description: NTPPlugInController.pluginDescription() ?? "",
            version: NTPPlugInController.pluginVersion() ?? "0",
            author: NTPPlugInController.pluginAuthor() ?? "",
            principalClass: NTPPlugInController.self,
            preferences: self,
            analyzerClass: NTPProtocolAnalyzer.self,
            attributes: attributes,
            targets: configuredTargetProtocols,
            columns: columns
        )

        let registered = controller.register(CPAPlugIn: plugin, forBundle: bundle)
        pluginStruct = plugin
        cpaLog("NTP plugin registered (\(registered)) for \(plugin.plugInTargetProtocols.count) protocol target(s)")
    }
}
