//
//  Random.swift
//  Reflect
//
//  Created by Trey Tartt on 1/9/22.
//

import Foundation
import SwiftUI
import SwiftData

struct Constants {
    static let groupShareId = "group.com.88oakapps.reflect"
    static let groupShareIdDebug = "group.com.88oakapps.reflect.debug"

    static var currentGroupShareId: String {
        #if DEBUG
        return groupShareIdDebug
        #else
        return groupShareId
        #endif
    }

    static let viewsCornerRaidus: CGFloat = 10
}

struct GroupUserDefaults {
    static var groupDefaults: UserDefaults {
#if DEBUG
        return UserDefaults(suiteName: Constants.groupShareIdDebug) ?? .standard
#else
        return UserDefaults(suiteName: Constants.groupShareId) ?? .standard
#endif
    }
}

class Random {
    static var tomorrowMidnightThirty: Date {
        let components = DateComponents(hour: 0, minute: 30, second: 0)
        var updateTime = Date()
        if let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date()),
           let tomorrowMorning = Calendar.current.date(byAdding: components, to: tomorrow) {
            updateTime = tomorrowMorning
        }
        return updateTime
    }
    
    static var existingWeekdayName = [Int: String]()
    static func weekdayName(fromDate date: Date) -> String {
        let weekday = Calendar.current.component(.weekday, from: date)
        let calendar = Calendar.current
        let dayIndex = ((weekday - 1) + (calendar.firstWeekday - 1)) % 7
        if let value = Random.existingWeekdayName[dayIndex] {
            return value
        }
        let newValue = calendar.weekdaySymbols[dayIndex]
        Random.existingWeekdayName[dayIndex] = newValue
        return newValue
    }
    
    /// Cached month symbols to avoid creating DateFormatter repeatedly
    private static let monthSymbols: [String] = DateFormatter().monthSymbols

    static func monthName(fromMonthInt: Int) -> String {
        return monthSymbols[fromMonthInt-1]
    }
    
    static var existingDayFormat = [NSNumber: String]()
    static func dayFormat(fromDate date: Date) -> String {
        let components = Calendar.current.dateComponents([.day], from: date)
        let day = components.day ?? 1
        
        let formatter = NumberFormatter()
        formatter.numberStyle = .ordinal
        let num = NSNumber(integerLiteral: day)
        if let value = existingDayFormat[num] {
            return value
        }
        let newValue = formatter.string(from: num) ?? ""
        existingDayFormat[num] = newValue
        return newValue
    }
    
    #if !os(watchOS)
    static func createTotalPerc(fromEntries entries: [MoodEntryModel]) -> [MoodMetrics] {
        let filteredEntries = entries.filter({
            return ![.missing, .placeholder].contains($0.mood)
        })
        var returnData = [MoodMetrics]()

        for (_, mood) in Mood.allValues.enumerated() {
            let moodEntries = filteredEntries.filter({
                $0.moodValue == mood.rawValue
            })
            let total = moodEntries.count
            let perc = (Float(total) / Float(filteredEntries.count)) * 100
            returnData.append(MoodMetrics(mood: mood, total: total, percent: perc))
        }

        returnData = returnData.sorted(by: {
            $0.mood.rawValue > $1.mood.rawValue
        })

        return returnData
    }
    #endif
}

#if !os(watchOS)
struct RoundedCorner: Shape {

    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape( RoundedCorner(radius: radius, corners: corners) )
    }

    func snapshot() -> UIImage {
        let controller = UIHostingController(rootView: self)
        let view = controller.view
        let targetSize = controller.view.intrinsicContentSize
        view?.bounds = CGRect(origin: .zero, size: targetSize)
        view?.backgroundColor = .clear
        let renderer = UIGraphicsImageRenderer(size: targetSize)
        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
    }

    func asImage(size: CGSize) -> UIImage {
        let wrapped = self.ignoresSafeArea().frame(width: size.width, height: size.height)
        let controller = UIHostingController(rootView: wrapped)
        controller.view.bounds = CGRect(origin: .zero, size: size)
        controller.view.backgroundColor = .clear
        controller.view.layoutIfNeeded()
        let image = controller.view.asImage()
        return image
    }
}

extension UIView {
    func asImage() -> UIImage {
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1
        return UIGraphicsImageRenderer(size: self.layer.frame.size, format: format).image { context in
            self.drawHierarchy(in: self.layer.bounds, afterScreenUpdates: true)
        }
    }
}

extension Color {
    static func random() -> Self {
        Self(
            red: .random(in: 0...1),
            green: .random(in: 0...1),
            blue: .random(in: 0...1)
        )
    }

    public func lighter(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).lighter(by: amount)) }
    public func darker(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).darker(by: amount)) }
}

extension String {
    /// Cache for rendered emoji images to avoid expensive re-rendering
    private static var textToImageCache = [String: UIImage]()

    func textToImage() -> UIImage? {
        // Return cached image if available
        if let cached = Self.textToImageCache[self] {
            return cached
        }

        let nsString = (self as NSString)
        let font = UIFont.systemFont(ofSize: 100) // you can change your font size here
        let stringAttributes = [NSAttributedString.Key.font: font]
        let imageSize = nsString.size(withAttributes: stringAttributes)

        UIGraphicsBeginImageContextWithOptions(imageSize, false, 0) //  begin image context
        UIColor.clear.set() // clear background
        UIRectFill(CGRect(origin: CGPoint(), size: imageSize)) // set rect size
        nsString.draw(at: CGPoint.zero, withAttributes: stringAttributes) // draw text within rect
        let image = UIGraphicsGetImageFromCurrentImageContext() // create image from context
        UIGraphicsEndImageContext() //  end image context

        let result = image ?? UIImage()

        // Cache the rendered image
        Self.textToImageCache[self] = result

        return result
    }
}

extension UIColor {

    func lighter(by percentage: CGFloat = 10.0) -> UIColor {
        return self.adjust(by: abs(percentage))
    }

    func darker(by percentage: CGFloat = 10.0) -> UIColor {
        return self.adjust(by: -abs(percentage))
    }

    func adjust(by percentage: CGFloat) -> UIColor {
        var alpha, hue, saturation, brightness, red, green, blue, white : CGFloat
        (alpha, hue, saturation, brightness, red, green, blue, white) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

        let multiplier = percentage / 100.0

        if self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
            let newBrightness: CGFloat = max(min(brightness + multiplier*brightness, 1.0), 0.0)
            return UIColor(hue: hue, saturation: saturation, brightness: newBrightness, alpha: alpha)
        }
        else if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
            let newRed: CGFloat = min(max(red + multiplier*red, 0.0), 1.0)
            let newGreen: CGFloat = min(max(green + multiplier*green, 0.0), 1.0)
            let newBlue: CGFloat = min(max(blue + multiplier*blue, 0.0), 1.0)
            return UIColor(red: newRed, green: newGreen, blue: newBlue, alpha: alpha)
        }
        else if self.getWhite(&white, alpha: &alpha) {
            let newWhite: CGFloat = (white + multiplier*white)
            return UIColor(white: newWhite, alpha: alpha)
        }

        return self
    }
}
#endif

// MARK: - Date Formatting Cache

/// High-performance date formatting cache to eliminate repeated ICU Calendar operations.
/// Caches formatted date strings keyed by (date's day start, format type).
final class DateFormattingCache {
    static let shared = DateFormattingCache()

    enum Format: Int {
        case day                    // "15"
        case weekdayWide            // "Monday"
        case weekdayAbbreviated     // "Mon"
        case weekdayWideDay         // "Monday 15"
        case monthWide              // "January"
        case monthAbbreviated       // "Jan"
        case monthAbbreviatedDay    // "Jan 15"
        case monthAbbreviatedYear   // "Jan 2025"
        case monthWideYear          // "January 2025"
        case weekdayAbbrevMonthAbbrev // "Mon Jan"
        case weekdayWideMonthAbbrev   // "Monday Jan"
        case yearMonthDayDigits       // "2025/01/15"
        case dateMedium               // "Jan 15, 2025" (dateStyle = .medium)
        case dateFull                 // "Monday, January 15, 2025" (dateStyle = .full)
    }

    private var cache = [Int: [Format: String]]()
    private let calendar = Calendar.current

    // Reusable formatters (creating DateFormatter is expensive)
    private lazy var dayFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "d"
        return f
    }()

    private lazy var weekdayWideFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEEE"
        return f
    }()

    private lazy var weekdayAbbrevFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEE"
        return f
    }()

    private lazy var weekdayWideDayFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEEE d"
        return f
    }()

    private lazy var monthWideFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "MMMM"
        return f
    }()

    private lazy var monthAbbrevFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "MMM"
        return f
    }()

    private lazy var monthAbbrevDayFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "MMM d"
        return f
    }()

    private lazy var monthAbbrevYearFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "MMM yyyy"
        return f
    }()

    private lazy var monthWideYearFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "MMMM yyyy"
        return f
    }()

    private lazy var weekdayAbbrevMonthAbbrevFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEE MMM"
        return f
    }()

    private lazy var weekdayWideMonthAbbrevFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "EEEE MMM"
        return f
    }()

    private lazy var yearMonthDayDigitsFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "yyyy/MM/dd"
        return f
    }()

    private lazy var dateMediumFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .medium
        return f
    }()

    private lazy var dateFullFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .full
        return f
    }()

    private init() {}

    /// Get cached formatted string for a date
    func string(for date: Date, format: Format) -> String {
        let dayKey = dayIdentifier(for: date)

        // Check cache
        if let formatCache = cache[dayKey], let cached = formatCache[format] {
            return cached
        }

        // Format and cache
        let formatted = formatDate(date, format: format)

        if cache[dayKey] == nil {
            cache[dayKey] = [:]
        }
        cache[dayKey]?[format] = formatted

        return formatted
    }

    private func dayIdentifier(for date: Date) -> Int {
        // Use days since reference date as unique key
        Int(calendar.startOfDay(for: date).timeIntervalSinceReferenceDate / 86400)
    }

    private func formatDate(_ date: Date, format: Format) -> String {
        switch format {
        case .day:
            return dayFormatter.string(from: date)
        case .weekdayWide:
            return weekdayWideFormatter.string(from: date)
        case .weekdayAbbreviated:
            return weekdayAbbrevFormatter.string(from: date)
        case .weekdayWideDay:
            return weekdayWideDayFormatter.string(from: date)
        case .monthWide:
            return monthWideFormatter.string(from: date)
        case .monthAbbreviated:
            return monthAbbrevFormatter.string(from: date)
        case .monthAbbreviatedDay:
            return monthAbbrevDayFormatter.string(from: date)
        case .monthAbbreviatedYear:
            return monthAbbrevYearFormatter.string(from: date)
        case .monthWideYear:
            return monthWideYearFormatter.string(from: date)
        case .weekdayAbbrevMonthAbbrev:
            return weekdayAbbrevMonthAbbrevFormatter.string(from: date)
        case .weekdayWideMonthAbbrev:
            return weekdayWideMonthAbbrevFormatter.string(from: date)
        case .yearMonthDayDigits:
            return yearMonthDayDigitsFormatter.string(from: date)
        case .dateMedium:
            return dateMediumFormatter.string(from: date)
        case .dateFull:
            return dateFullFormatter.string(from: date)
        }
    }
}

extension Bundle {
    var appName: String {
        return infoDictionary?["CFBundleName"] as? String ?? "Reflect"
    }

    var bundleId: String {
        return bundleIdentifier ?? "com.88oakapps.reflect"
    }

    var versionNumber: String {
        return infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0"
    }

    var buildNumber: String {
        return infoDictionary?["CFBundleVersion"] as? String ?? "0"
    }
}
