//
//  PeriodicWorker.swift
//  Tun
//
//  Created by Codex on 2026/5/20.
//

import Foundation

public actor PeriodicWorker {
    public typealias Operation = @Sendable () async throws -> Void
    public typealias ErrorHandler = @Sendable (Error) async -> Void

    public enum Mode: Sendable {
        case fixedDelay
        case fixedRate
    }

    public enum ErrorPolicy: Sendable {
        case keepRunning(delay: Duration?)
        case stop
    }

    public struct Configuration: Sendable {
        public var interval: Duration
        public var tolerance: Duration?
        public var runImmediately: Bool
        public var mode: Mode
        public var errorPolicy: ErrorPolicy

        public init(
            interval: Duration,
            tolerance: Duration? = nil,
            runImmediately: Bool = true,
            mode: Mode = .fixedDelay,
            errorPolicy: ErrorPolicy = .keepRunning(delay: .seconds(5))
        ) {
            self.interval = interval
            self.tolerance = tolerance
            self.runImmediately = runImmediately
            self.mode = mode
            self.errorPolicy = errorPolicy
        }
    }

    private let configuration: Configuration
    private let operation: Operation
    private let onError: ErrorHandler

    private var task: Task<Void, Never>?
    private var generation: UInt64 = 0

    public init(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler = { _ in }) {
        self.configuration = configuration
        self.operation = operation
        self.onError = onError
    }

    public func start() {
        guard self.task == nil else {
            return
        }

        self.generation &+= 1
        let currentGeneration = self.generation
        let configuration = self.configuration
        let operation = self.operation
        let onError = self.onError

        self.task = Task {
            switch configuration.mode {
            case .fixedDelay:
                await Self.runFixedDelay(
                    configuration: configuration,
                    operation: operation,
                    onError: onError
                )
            case .fixedRate:
                await Self.runFixedRate(
                    configuration: configuration,
                    operation: operation,
                    onError: onError
                )
            }
            self.clearIfCurrent(generation: currentGeneration)
        }
    }

    public func stop() async {
        self.generation &+= 1

        let task = self.task
        self.task = nil

        task?.cancel()
        await task?.value
    }

    public var isRunning: Bool {
        self.task != nil
    }

    private func clearIfCurrent(generation: UInt64) {
        guard self.generation == generation else {
            return
        }

        self.task = nil
    }

    private static func runFixedDelay(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler) async {
        if !configuration.runImmediately {
            guard await sleep(interval: configuration.interval, tolerance: configuration.tolerance) else {
                return
            }
        }

        while !Task.isCancelled {
            let shouldContinue = await runOperation(
                operation: operation,
                onError: onError,
                errorPolicy: configuration.errorPolicy
            )
            guard shouldContinue else {
                return
            }

            guard await sleep(interval: configuration.interval, tolerance: configuration.tolerance) else {
                return
            }
        }
    }

    private static func runFixedRate(configuration: Configuration, operation: @escaping Operation, onError: @escaping ErrorHandler) async {
        let clock = ContinuousClock()
        var nextRun = clock.now
        if !configuration.runImmediately {
            nextRun = nextRun.advanced(by: configuration.interval)
        }

        while !Task.isCancelled {
            let now = clock.now
            if nextRun > now {
                let delay = now.duration(to: nextRun)
                guard await sleep(interval: delay, tolerance: configuration.tolerance) else {
                    return
                }
            }

            let shouldContinue = await runOperation(
                operation: operation,
                onError: onError,
                errorPolicy: configuration.errorPolicy
            )
            guard shouldContinue else {
                return
            }

            let afterRun = clock.now
            repeat {
                nextRun = nextRun.advanced(by: configuration.interval)
            } while nextRun <= afterRun
        }
    }

    private static func runOperation(operation: @escaping Operation, onError: @escaping ErrorHandler, errorPolicy: ErrorPolicy) async -> Bool {
        do {
            try Task.checkCancellation()
            try await operation()
            return true
        } catch is CancellationError {
            return false
        } catch {
            await onError(error)

            switch errorPolicy {
            case .keepRunning(let delay):
                if let delay {
                    return await sleep(interval: delay, tolerance: nil)
                }
                return true
            case .stop:
                return false
            }
        }
    }

    private static func sleep(interval: Duration, tolerance: Duration?) async -> Bool {
        do {
            try await Task.sleep(for: interval, tolerance: tolerance)
            return true
        } catch is CancellationError {
            return false
        } catch {
            return false
        }
    }
}
