import ForgejoKit
import SwiftUI

/// Filter options surfaced in the Actions tab. The raw value is what gets
/// sent on the Forgejo `status` query parameter; `nil` means no filter.
enum WorkflowRunFilter: String, Hashable, CaseIterable {
    case all
    case running
    case success
    case failure

    var label: String {
        switch self {
        case .all: "All"
        case .running: "Running"
        case .success: "Success"
        case .failure: "Failed"
        }
    }

    /// The exact value Forgejo's `status` query param accepts, or `nil` for All.
    var apiStatus: String? {
        switch self {
        case .all: nil
        case .running: "running"
        case .success: "success"
        case .failure: "failure"
        }
    }
}

struct WorkflowStatusStyle {
    let symbol: String
    let tint: Color
    let isAnimated: Bool
    let label: String

    /// Maps Forgejo's single status value to a UI style. Used for runs, jobs,
    /// and steps, they share the same status vocabulary. Forgejo's known
    /// statuses: success, failure, cancelled, skipped, running, waiting,
    /// blocked, unknown. Anything else falls back to a question-mark glyph.
    static func forStatus(_ status: String) -> WorkflowStatusStyle {
        switch status {
        case "success":
            .init(symbol: "checkmark.circle.fill", tint: .green, isAnimated: false, label: "Success")
        case "failure":
            .init(symbol: "xmark.circle.fill", tint: .red, isAnimated: false, label: "Failed")
        case "cancelled":
            .init(symbol: "slash.circle.fill", tint: .secondary, isAnimated: false, label: "Cancelled")
        case "skipped":
            .init(symbol: "forward.circle.fill", tint: .secondary, isAnimated: false, label: "Skipped")
        case "running":
            .init(symbol: "circle.dotted", tint: .blue, isAnimated: true, label: "Running")
        case "waiting":
            .init(symbol: "clock", tint: .secondary, isAnimated: false, label: "Waiting")
        case "blocked":
            .init(symbol: "hand.raised.fill", tint: .orange, isAnimated: false, label: "Blocked")
        case "unknown":
            .init(symbol: "questionmark.circle", tint: .secondary, isAnimated: false, label: "Unknown")
        default:
            .init(symbol: "questionmark.circle", tint: .secondary, isAnimated: false, label: status.capitalized)
        }
    }
}

struct WorkflowStatusIcon: View {
    let status: String

    var body: some View {
        let style = WorkflowStatusStyle.forStatus(status)
        Image(systemName: style.symbol)
            .foregroundStyle(style.tint)
            .symbolEffect(
                .pulse,
                options: style.isAnimated ? .repeating : .nonRepeating,
                isActive: style.isAnimated,
            )
    }
}

extension WorkflowRun {
    var statusStyle: WorkflowStatusStyle {
        WorkflowStatusStyle.forStatus(status)
    }

    var shortCommitSha: String? {
        guard let commitSha, commitSha.count >= 7 else { return commitSha }
        return String(commitSha.prefix(7))
    }

    var displayName: String {
        if let title, !title.isEmpty { return title }
        if let workflowId, !workflowId.isEmpty { return workflowId }
        if let indexInRepo { return "Run #\(indexInRepo)" }
        return "Run \(id)"
    }

    /// Forgejo serialises an unset Go `time.Time` as `0001-01-01T00:00:00Z`,
    /// which decodes to a sub-1970 `Date`. Treat anything before 2000 as nil
    /// so the UI doesn't render "56 years ago".
    var startedDisplay: Date? {
        sanitised(started)
    }

    var stoppedDisplay: Date? {
        sanitised(stopped)
    }

    var updatedDisplay: Date? {
        sanitised(updated)
    }

    var createdDisplay: Date? {
        sanitised(created)
    }

    private static let saneEpoch = Date(timeIntervalSince1970: 946_684_800)

    private func sanitised(_ date: Date?) -> Date? {
        guard let date, date >= Self.saneEpoch else { return nil }
        return date
    }

    /// Elapsed time as TimeInterval. Forgejo serialises a Go `time.Duration`
    /// , int64 nanoseconds, so divide by 1_000_000_000. Falls back to the
    /// timestamps only when both are sane (post-2000).
    var duration: TimeInterval? {
        if let nanos = durationNanos, nanos > 0 {
            return TimeInterval(nanos) / 1_000_000_000
        }
        if let start = startedDisplay, let stop = stoppedDisplay, stop > start {
            return stop.timeIntervalSince(start)
        }
        return nil
    }
}
