// SPDX-FileCopyrightText: 2026 Brent Daignault
// SPDX-License-Identifier: GPL-3.0-or-later

import CoreGraphics
import Foundation

// ── Label sequence ───────────────────────────────────────────────────────────

enum LabelSequence {
    case rowOrder       // labels alternate columns left-to-right across each row
    case columnOrder    // each column fills completely before the next
}

// ── Colour ───────────────────────────────────────────────────────────────────
// CGFloat throughout — feeds directly into CGColor, which takes CGFloat.

struct RGB {
    var r: CGFloat
    var g: CGFloat
    var b: CGFloat

    var cgColor: CGColor {
        CGColor(srgbRed: r, green: g, blue: b, alpha: 1.0)
    }
}

// ── Config ───────────────────────────────────────────────────────────────────

struct Config {
    var titleLines: [String] = []
    var subtitleLines: [String] = []
    var titleFontSize: CGFloat = 20.0
    var titleColor = RGB(r: 0.10, g: 0.23, b: 0.36)
    var subtitleFontSize: CGFloat = 10.0
    var subtitleColor = RGB(r: 0.33, g: 0.33, b: 0.33)
    var labelSequence: LabelSequence = .rowOrder

    var coverImage = "cover.png"
    var committeeImage = "committee.png"
    var scheduleImage = "schedule.png"
    var memberCSV = "members.csv"
    var bookletPDF = "booklet.pdf"
    var standardPDF = "standard.pdf"
}

// ── Config file parser ──────────────────────────────────────────────────────
//
// Format:
//   # comment
//   Key:              <- bare key opens a text block
//   First line        <- block lines, collected until a blank line
//   Second line
//                     <- blank line closes the block
//   Key: single value <- single-value entry
//
// Keys matched case-insensitively.  Colour values as #rrggbb hex.
// Filenames may optionally be surrounded by double quotes.

enum ConfigLoader {
    static let fileName = "roster.sdd"

    static func load() -> Config {
        var cfg = Config()

        guard let data = FileManager.default.contents(atPath: fileName),
              let text = String(data: data, encoding: .utf8) else {
            warn("cannot open \(fileName) — using defaults")
            return cfg
        }

        var blockKey: String?
        var blockLines: [String] = []

        func closeBlock() {
            guard let key = blockKey else { return }
            apply(&cfg, key: key, block: blockLines)
            blockKey = nil
            blockLines = []
        }

        for rawLine in text.components(separatedBy: .newlines) {
            let trimmed = rawLine.trimmingCharacters(in: .whitespaces)

            if trimmed.isEmpty {
                closeBlock()
                continue
            }
            if trimmed.hasPrefix("#") {
                continue
            }
            if blockKey != nil {
                blockLines.append(trimmed)
                continue
            }
            guard let colonIndex = trimmed.firstIndex(of: ":") else {
                warn("no colon: \(trimmed)")
                continue
            }
            let key = String(trimmed[trimmed.startIndex..<colonIndex])
                .trimmingCharacters(in: .whitespaces)
            let value = String(trimmed[trimmed.index(after: colonIndex)...])
                .trimmingCharacters(in: .whitespaces)

            if value.isEmpty {
                blockKey = key
                blockLines = []
            } else {
                applySingleValue(&cfg, key: key, value: value)
            }
        }
        closeBlock()
        return cfg
    }

    /// Applies a block-collected key (Title, Subtitle).
    private static func apply(_ cfg: inout Config, key: String, block: [String]) {
        switch key.lowercased() {
        case "title":
            cfg.titleLines = block
        case "subtitle":
            cfg.subtitleLines = block
        default:
            warn("unknown block key '\(key)'")
        }
    }

    /// Applies a single-value key.
    private static func applySingleValue(_ cfg: inout Config, key: String, value: String) {
        switch key.lowercased() {
        case "title font size":
            if let d = Double(value) { cfg.titleFontSize = CGFloat(d) }
        case "title color":
            if let c = parseColor(value) { cfg.titleColor = c }
        case "subtitle font size":
            if let d = Double(value) { cfg.subtitleFontSize = CGFloat(d) }
        case "subtitle color":
            if let c = parseColor(value) { cfg.subtitleColor = c }
        case "label sequence":
            cfg.labelSequence = (value.lowercased() == "column order") ? .columnOrder : .rowOrder
        case "cover image":
            cfg.coverImage = stripQuotes(value)
        case "committee image":
            cfg.committeeImage = stripQuotes(value)
        case "schedule image":
            cfg.scheduleImage = stripQuotes(value)
        case "member csv":
            cfg.memberCSV = stripQuotes(value)
        case "booklet pdf":
            cfg.bookletPDF = stripQuotes(value)
        case "standard pdf":
            cfg.standardPDF = stripQuotes(value)
        default:
            warn("unknown key '\(key)'")
        }
    }

    private static func stripQuotes(_ s: String) -> String {
        guard s.count >= 2, s.hasPrefix("\""), s.hasSuffix("\"") else { return s }
        return String(s.dropFirst().dropLast())
    }

    private static func parseColor(_ s: String) -> RGB? {
        var hex = s
        if hex.hasPrefix("#") { hex.removeFirst() }
        guard hex.count == 6, let value = UInt32(hex, radix: 16) else { return nil }
        let r = CGFloat((value >> 16) & 0xFF) / 255.0
        let g = CGFloat((value >> 8) & 0xFF) / 255.0
        let b = CGFloat(value & 0xFF) / 255.0
        return RGB(r: r, g: g, b: b)
    }

    private static func warn(_ message: String) {
        FileHandle.standardError.write("\(fileName): \(message)\n".data(using: .utf8)!)
    }
}
