import Foundation
import SwiftTUI

// MARK: - Models

enum AgeGroup: Int, CustomStringConvertible, CaseIterable {
    case junior = 1
    case adult = 2
    case senior = 3

    var description: String {
        switch self {
        case .junior: return "Junior"
        case .adult: return "Adult"
        case .senior: return "Senior"
        }
    }
}

struct Runner {
    let name: String
    let ageGroup: AgeGroup
    let time: String
    let number: Int
}

enum SortKey: String, CaseIterable {
    case name
    case ageGroup
    case time
    case number

    func sort(_ lhs: Runner, _ rhs: Runner) -> Bool {
        switch self {
        case .name:     return lhs.name < rhs.name
        case .ageGroup: return lhs.ageGroup.rawValue < rhs.ageGroup.rawValue
        case .time:     return lhs.time < rhs.time
        case .number:   return lhs.number < rhs.number
        }
    }
}

// MARK: - IO

func readFile(_ path: String) -> String {
    (try? String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8)) ?? ""
}

// MARK: - Parsing

func parseResults(_ content: String, separator: String) -> [Int: String] {
    let pairs = content
        .components(separatedBy: .newlines)
        .compactMap { line -> (Int, String)? in
            let cols = line.components(separatedBy: separator)
            guard cols.count >= 2, let key = Int(cols[0]) else { return nil }
            return (key, cols[1])
        }
    return Dictionary(pairs, uniquingKeysWith: { _, last in last })
}

func parseStarters(_ content: String, separator: String) -> [(nr: Int, ageGroup: Int, name: String)] {
    content
        .components(separatedBy: .newlines)
        .compactMap { line in
            let cols = line.components(separatedBy: separator)
            guard cols.count >= 3, let nr = Int(cols[0]), let ageGroup = Int(cols[1])
            else { return nil }
            return (nr: nr, ageGroup: ageGroup, name: cols[2])
        }
}

func assembleRunners(starters: [(nr: Int, ageGroup: Int, name: String)], results: [Int: String])
    -> [Runner]
{
    starters.compactMap { starter in
        guard let time = results[starter.nr],
            let group = AgeGroup(rawValue: starter.ageGroup)
        else {
            return nil
        }
        return Runner(name: starter.name, ageGroup: group, time: time, number: starter.nr)
    }
}

func nonParticipants(starters: [(nr: Int, ageGroup: Int, name: String)], results: [Int: String])
    -> [Runner]
{
    starters.compactMap { starter in
        guard results[starter.nr] == nil,
            let group = AgeGroup(rawValue: starter.ageGroup)
        else {
            return nil
        }
        return Runner(name: starter.name, ageGroup: group, time: "DNS", number: starter.nr)
    }
}

// MARK: - Transforms

func visibleRunners(
    _ runners: [Runner],
    query: String,
    includedGroups: Set<AgeGroup>,
    sortKey: SortKey
) -> [Runner] {
    let needle = query.lowercased()
    return runners
        .filter { includedGroups.contains($0.ageGroup) }
        .filter { needle.isEmpty || $0.name.lowercased().contains(needle) }
        .sorted(by: sortKey.sort)
}

func toggling(_ groups: Set<AgeGroup>, _ group: AgeGroup) -> Set<AgeGroup> {
    groups.contains(group) ? groups.subtracting([group]) : groups.union([group])
}

// MARK: - Formatting

func menuLabel(_ text: String, on: Bool) -> String {
    on ? "[\(text)]" : " \(text) "
}

func formatRow(_ nr: String, _ name: String, _ group: String, _ time: String) -> String {
    nr.padding(toLength: 6, withPad: " ", startingAt: 0)
        + name.padding(toLength: 22, withPad: " ", startingAt: 0)
        + group.padding(toLength: 9, withPad: " ", startingAt: 0)
        + time
}

// MARK: - Configuration

let resultsURL = "/Users/cediackermann/Projects/m323/Sources/MiniProject/data/results.txt"
let startListURL = "/Users/cediackermann/Projects/m323/Sources/MiniProject/data/startList.txt"

// MARK: - Entry Point

let results = parseResults(readFile(resultsURL), separator: "\t")
let starters = parseStarters(readFile(startListURL), separator: "\t")
let finished = assembleRunners(starters: starters, results: results)
let absent = nonParticipants(starters: starters, results: results)

Application(rootView: RunnerBrowser(finished: finished, absent: absent)).start()

// MARK: - TUI

struct RunnerBrowser: View {
    let finished: [Runner]
    let absent: [Runner]

    @State private var query: String = ""
    @State private var sortKey: SortKey = .number
    @State private var includedGroups: Set<AgeGroup> = Set(AgeGroup.allCases)
    @State private var showAbsent: Bool = false

    private var source: [Runner] {
        showAbsent ? absent : finished
    }

    private var visible: [Runner] {
        visibleRunners(source, query: query, includedGroups: includedGroups, sortKey: sortKey)
    }

    var body: some View {
        VStack(alignment: .leading) {
            Text("Stadt-Rundlauf - \(visible.count)/\(source.count) runners")
                .bold()

            HStack {
                Text("Show:")
                Button(menuLabel("Finished", on: !showAbsent)) {
                    showAbsent = false
                }
                Button(menuLabel("Did not start", on: showAbsent)) {
                    showAbsent = true
                }
            }

            HStack {
                Text("Sort:")
                ForEach(SortKey.allCases, id: \.rawValue) { key in
                    Button(menuLabel(key.rawValue, on: key == sortKey)) {
                        sortKey = key
                    }
                }
            }

            HStack {
                Text("Groups:")
                ForEach(AgeGroup.allCases, id: \.rawValue) { group in
                    Button(menuLabel("\(group)", on: includedGroups.contains(group))) {
                        includedGroups = toggling(includedGroups, group)
                    }
                }
            }

            TextField(placeholder: "Search by name…") { value in
                query = value
            }

            Divider()

            Text(formatRow("Nr", "Name", "Group", "Time")).bold()

            ScrollView {
                ForEach(visible, id: \.number) { runner in
                    Text(formatRow("#\(runner.number)", runner.name, "\(runner.ageGroup)", runner.time))
                }
            }
        }
        .padding()
    }
}
