//
//  ChartDataBuildable.swift
//  Reflect
//
//  Created by Trey Tartt on 1/17/22.
//

import SwiftUI

typealias Year = Int
typealias Month = Int

protocol ChartDataBuildable {
    associatedtype ChartType: ChartViewItemBuildable
    // [Year: [Month: [View]]
    func buildGridData(withData data: [MoodEntryModel]) -> [Year: [Month: [ChartType]]]
}

extension ChartDataBuildable {
    public func buildGridData(withData data: [MoodEntryModel]) ->  [Year: [Month: [ChartType]]] {
        guard let earliestEntry = data.first,
              let lastEntry = data.last else { return [:] }

        let calendar = Calendar.current

        // Pre-group entries by year/month/day in a single pass - O(n) instead of O(n*m)
        // Key: "year-month-day" -> entry
        var entriesByDate = [String: MoodEntryModel]()
        for entry in data {
            let components = calendar.dateComponents([.year, .month, .day], from: entry.forDate)
            let key = "\(components.year!)-\(components.month!)-\(components.day!)"
            entriesByDate[key] = entry
        }

        let earliestYear = calendar.component(.year, from: earliestEntry.forDate)
        let latestYear = calendar.component(.year, from: lastEntry.forDate)

        // Cache expensive lookups
        let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
        let shape = UserDefaultsStore.getCustomBGShape()

        var returnData = [Int: [Int: [ChartType]]]()

        for year in earliestYear...latestYear {
            var allMonths = [Int: [ChartType]]()

            for month in 1...12 {
                var components = DateComponents()
                components.month = month
                components.year = year
                guard let startDateOfMonth = calendar.date(from: components) else { continue }

                allMonths[month] = createViewFor(
                    year: year,
                    month: month,
                    forMonth: startDateOfMonth,
                    entriesByDate: entriesByDate,
                    moodTint: moodTint,
                    shape: shape,
                    calendar: calendar
                )
            }
            returnData[year] = allMonths
        }

        return returnData
    }

    private func createViewFor(
        year: Int,
        month: Int,
        forMonth monthDate: Date,
        entriesByDate: [String: MoodEntryModel],
        moodTint: MoodTintable.Type,
        shape: BGShape,
        calendar: Calendar
    ) -> [ChartType] {
        var filledOutArray = [ChartType]()

        let range = calendar.range(of: .day, in: .month, for: monthDate)!
        let numDays = range.count

        for day in 1...numDays {
            let key = "\(year)-\(month)-\(day)"
            if let item = entriesByDate[key] {
                // O(1) dictionary lookup instead of O(n) filter
                let view = ChartType(color: moodTint.color(forMood: item.mood),
                                     weekDay: Int(item.weekDay),
                                     shape: shape)
                filledOutArray.append(view)
            } else {
                let thisDate = calendar.date(bySetting: .day, value: day, of: monthDate)!
                let view = ChartType(color: Mood.placeholder.color,
                                     weekDay: calendar.component(.weekday, from: thisDate),
                                     shape: shape)
                filledOutArray.append(view)
            }
        }

        for _ in filledOutArray.count...32 {
            let view = ChartType(color: Mood.placeholder.color,
                                 weekDay: 2,
                                 shape: shape)
            filledOutArray.append(view)
        }

        return filledOutArray
    }
}

struct DayChartViewChartBuilder: ChartDataBuildable {
    typealias ChartType = DayChartView
}
