import SwiftUI

struct ComponentLibraryItem: Identifiable, Equatable {
    let id: String
    let name: String
    let translations: [String: String]
    let voltageIn: Double?
    let voltageOut: Double?
    let watt: Double?
    let dutyCyclePercent: Double?
    let defaultUtilizationFactorPercent: Double?
    let iconURL: URL?

    var displayVoltage: Double? {
        voltageIn ?? voltageOut
    }

    var current: Double? {
        guard let power = watt, let voltage = displayVoltage, voltage > 0 else { return nil }
        return power / voltage
    }
    
    var voltageLabel: String? {
        guard let voltage = displayVoltage else { return nil }
        return String(format: "%.1fV", voltage)
    }
    
    var powerLabel: String? {
        guard let power = watt else { return nil }
        return String(format: "%.0fW", power)
    }
    
    var currentLabel: String? {
        guard let current else { return nil }
        return String(format: "%.1fA", current)
    }

    var normalizedDutyCyclePercent: Double? {
        Self.normalizePercentValue(dutyCyclePercent)
    }

    var normalizedUtilizationFactorPercent: Double? {
        Self.normalizePercentValue(defaultUtilizationFactorPercent)
    }

    var defaultDailyUsageHours: Double? {
        guard let percent = normalizedUtilizationFactorPercent else { return nil }
        return (percent / 100) * 24
    }

    var localizedName: String {
        localizedName(usingPreferredLanguages: Locale.preferredLanguages) ?? name
    }

    func localizedName(usingPreferredLanguages languages: [String]) -> String? {
        guard let primaryIdentifier = languages.first else { return nil }
        let locale = Locale(identifier: primaryIdentifier)
        return translation(for: locale)
    }

    func localizedName(for locale: Locale) -> String {
        translation(for: locale) ?? name
    }

    private func translation(for locale: Locale) -> String? {
        guard !translations.isEmpty else { return nil }

        let lookupKeys = ComponentLibraryItem.lookupKeys(for: locale)

        for key in lookupKeys {
            if let match = translations[key] {
                return match
            }
        }

        let normalizedTranslations = translations.reduce(into: [String: String]()) { result, element in
            let normalizedKey = ComponentLibraryItem.normalizeLocaleKey(element.key)
            result[normalizedKey] = element.value

            if let languageOnlyKey = ComponentLibraryItem.languageComponent(fromNormalizedKey: normalizedKey),
               result[languageOnlyKey] == nil {
                result[languageOnlyKey] = element.value
            }
        }

        for key in lookupKeys {
            let normalizedKey = ComponentLibraryItem.normalizeLocaleKey(key)
            if let match = normalizedTranslations[normalizedKey] {
                return match
            }
        }

        return nil
    }

    private static func lookupKeys(for locale: Locale) -> [String] {
        var keys: [String] = []

        func append(_ value: String?) {
            guard let value, !value.isEmpty else { return }

            for variant in variants(for: value) {
                if !keys.contains(variant) {
                    keys.append(variant)
                }
            }
        }

        append(locale.identifier)

        if let languageCode = locale.language.languageCode?.identifier.lowercased() {
            append(languageCode)
        }

        if let regionIdentifier = locale.region?.identifier.uppercased(),
           let languageIdentifier = locale.language.languageCode?.identifier.lowercased() {
            append("\(languageIdentifier)_\(regionIdentifier)")
        }

        return keys
    }

    private static func normalizeLocaleKey(_ key: String) -> String {
        let sanitized = key.replacingOccurrences(of: "-", with: "_")
        let parts = sanitized.split(separator: "_", omittingEmptySubsequences: true)

        guard let languagePart = parts.first else {
            return sanitized.lowercased()
        }

        let language = languagePart.lowercased()

        if parts.count >= 2, let regionPart = parts.last {
            return "\(language)_\(regionPart.uppercased())"
        }

        return language
    }

    private static func languageComponent(fromNormalizedKey key: String) -> String? {
        let components = key.split(separator: "_", omittingEmptySubsequences: true)
        guard let language = components.first else { return nil }
        return String(language)
    }

    private static func variants(for key: String) -> [String] {
        var collected: [String] = []
        let underscore = key.replacingOccurrences(of: "-", with: "_")
        let hyphen = key.replacingOccurrences(of: "_", with: "-")

        for candidate in Set([key, underscore, hyphen]) {
            collected.append(candidate)
        }

        return collected
    }

    private static func normalizePercentValue(_ value: Double?) -> Double? {
        guard var percent = value else { return nil }
        if percent <= 0 {
            // Backend sends 0 to represent 100% utilization.
            percent = 100
        }
        return min(max(percent, 0), 100)
    }
}

@MainActor
final class ComponentLibraryViewModel: ObservableObject {
    @Published private(set) var isLoading = false
    @Published private(set) var items: [ComponentLibraryItem] = []
    @Published private(set) var errorMessage: String?

    private let baseURL = URL(string: "https://base.voltplan.app")!
    private let urlSession: URLSession

    init(urlSession: URLSession = .shared) {
        self.urlSession = urlSession
    }

    init(previewItems: [ComponentLibraryItem]) {
        self.urlSession = .shared
        self.items = previewItems
        self.isLoading = false
    }

    func load() async {
        guard !isLoading else { return }
        isLoading = true
        errorMessage = nil

        do {
            let fetchedItems = try await fetchComponents()
            items = fetchedItems
        } catch {
            items = []
            errorMessage = error.localizedDescription
        }

        isLoading = false
    }

    func refresh() async {
        isLoading = false
        await load()
    }

    private func fetchComponents() async throws -> [ComponentLibraryItem] {
        let perPage = 200
        var page = 1
        var allRecords: [PocketBaseRecord] = []

        while true {
            var components = URLComponents(
                url: baseURL.appendingPathComponent("api/collections/components/records"),
                resolvingAgainstBaseURL: false
            )
            components?.queryItems = [
                URLQueryItem(name: "filter", value: "(type='load')"),
                URLQueryItem(name: "sort", value: "+name"),
                URLQueryItem(
                    name: "fields",
                    value: "id,collectionId,name,translations,icon,voltage_in,voltage_out,watt,duty_cycle,default_utilization_factor"
                ),
                URLQueryItem(name: "page", value: "\(page)"),
                URLQueryItem(name: "perPage", value: "\(perPage)")
            ]

            guard let url = components?.url else {
                throw URLError(.badURL)
            }

            var request = URLRequest(url: url)
            request.cachePolicy = .reloadIgnoringLocalCacheData

            let (data, response) = try await urlSession.data(for: request)

            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }

            let decoded = try JSONDecoder().decode(PocketBaseResponse.self, from: data)
            allRecords.append(contentsOf: decoded.items)

            let isLastPage: Bool
            if let totalPages = decoded.totalPages, totalPages > 0 {
                isLastPage = page >= totalPages
            } else {
                isLastPage = decoded.items.count < perPage
            }

            if isLastPage {
                break
            }

            page += 1
        }

        let mappedItems = allRecords.map { record in
            ComponentLibraryItem(
                id: record.id,
                name: record.name,
                translations: record.translations?.flattened ?? [:],
                voltageIn: record.voltageIn,
                voltageOut: record.voltageOut,
                watt: record.watt,
                dutyCyclePercent: record.dutyCycle,
                defaultUtilizationFactorPercent: record.defaultUtilizationFactor,
                iconURL: iconURL(for: record)
            )
        }
        for item in mappedItems {
            if let url = item.iconURL {
                Task.detached(priority: .background) {
                    await IconCache.shared.prefetch(url)
                }
            }
        }
        return mappedItems
    }

    private func iconURL(for record: PocketBaseRecord) -> URL? {
        guard let icon = record.icon else { return nil }

        return baseURL
            .appendingPathComponent("api")
            .appendingPathComponent("files")
            .appendingPathComponent(record.collectionId)
            .appendingPathComponent(record.id)
            .appendingPathComponent(icon)
    }

    private func escapeFilterValue(_ value: String) -> String {
        value.replacingOccurrences(of: "'", with: "\\'")
    }

    private struct PocketBaseResponse: Decodable {
        let page: Int?
        let perPage: Int?
        let totalPages: Int?
        let items: [PocketBaseRecord]
    }

    private struct PocketBaseRecord: Decodable {
        let id: String
        let collectionId: String
        let name: String
        let translations: TranslationsContainer?
        let icon: String?
        let voltageIn: Double?
        let voltageOut: Double?
        let watt: Double?
        let dutyCycle: Double?
        let defaultUtilizationFactor: Double?

        enum CodingKeys: String, CodingKey {
            case id
            case collectionId
            case name
            case translations
            case icon
            case voltageIn = "voltage_in"
            case voltageOut = "voltage_out"
            case watt
            case dutyCycle = "duty_cycle"
            case defaultUtilizationFactor = "default_utilization_factor"
        }

        struct TranslationsContainer: Decodable {
            private let storage: [String: TranslationValue]

            init(from decoder: Decoder) throws {
                let container = try decoder.singleValueContainer()
                storage = try container.decode([String: TranslationValue].self)
            }

            var flattened: [String: String] {
                storage.reduce(into: [:]) { result, entry in
                    if let value = entry.value.flattened {
                        result[entry.key] = value
                    }
                }
            }
        }

        private enum TranslationValue: Decodable {
            case string(String)
            case dictionary([String: String])

            init(from decoder: Decoder) throws {
                let singleValue = try decoder.singleValueContainer()
                if let string = try? singleValue.decode(String.self) {
                    self = .string(string)
                    return
                }

                if let dictionary = try? singleValue.decode([String: String].self) {
                    self = .dictionary(dictionary)
                    return
                }

                self = .dictionary([:])
            }

            var flattened: String? {
                switch self {
                case .string(let value):
                    return value.isEmpty ? nil : value
                case .dictionary(let dictionary):
                    if let name = dictionary["name"], !name.isEmpty {
                        return name
                    }
                    if let value = dictionary["value"], !value.isEmpty {
                        return value
                    }
                    return dictionary.values.first(where: { !$0.isEmpty })
                }
            }
        }
    }

}

struct ComponentLibraryView: View {
    @Environment(\.dismiss) private var dismiss
    @StateObject private var viewModel: ComponentLibraryViewModel
    @State private var searchText: String = ""
    let onSelect: (ComponentLibraryItem) -> Void

    init(onSelect: @escaping (ComponentLibraryItem) -> Void) {
        self._viewModel = StateObject(wrappedValue: ComponentLibraryViewModel())
        self.onSelect = onSelect
    }

    init(viewModel: ComponentLibraryViewModel, onSelect: @escaping (ComponentLibraryItem) -> Void) {
        self._viewModel = StateObject(wrappedValue: viewModel)
        self.onSelect = onSelect
    }
    
    var body: some View {
        NavigationStack {
            content
                .navigationTitle("VoltPlan Library")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("Close") {
                            dismiss()
                        }
                        .accessibilityIdentifier("library-view-close-button")
                    }
                }
        }
        .task {
            await viewModel.load()
        }
        .refreshable {
            await viewModel.refresh()
        }
        .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search components")
    }

    @ViewBuilder
    private var content: some View {
        if viewModel.isLoading && viewModel.items.isEmpty {
            ProgressView("Loading components")
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        } else if let errorMessage = viewModel.errorMessage {
            VStack(spacing: 12) {
                Image(systemName: "exclamationmark.triangle.fill")
                    .font(.system(size: 32))
                    .foregroundColor(.orange)
                Text("Unable to load components")
                    .font(.headline)
                Text(errorMessage)
                    .font(.caption)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.center)
                Button("Retry") {
                    Task { await viewModel.refresh() }
                }
                .buttonStyle(.borderedProminent)
            }
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        } else if filteredItems.isEmpty {
            VStack(spacing: 12) {
                Image(systemName: searchText.isEmpty ? "sparkles.rectangle.stack" : "magnifyingglass")
                    .font(.system(size: 32))
                    .foregroundColor(.secondary)
                Text(searchText.isEmpty ? "No components available" : "No matches")
                    .font(.headline)
                Text(searchText.isEmpty ? "Check back soon for new loads from VoltPlan." : "Try searching for a different name.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            .padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        } else {
            List {
                ForEach(filteredItems) { item in
                    Button {
                        onSelect(item)
                        dismiss()
                    } label: {
                        ComponentRow(item: item)
                    }
                    .buttonStyle(.plain)
                }
                poweredByVoltplanRow
            }
            .listStyle(.insetGrouped)
        }
    }

    private var filteredItems: [ComponentLibraryItem] {
        let trimmedQuery = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedQuery.isEmpty else { return viewModel.items }

        return viewModel.items.filter { item in
            let localizedName = item.localizedName
            return localizedName.localizedCaseInsensitiveContains(trimmedQuery)
                || item.name.localizedCaseInsensitiveContains(trimmedQuery)
        }
    }

    @ViewBuilder
    private var poweredByVoltplanRow: some View {
        if let url = URL(string: "https://voltplan.app") {
            Section {
                Link(destination: url) {
                    Image("PoweredByVoltplan")
                        .renderingMode(.original)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: 220)
                        .padding(.vertical, 20)
                        .frame(maxWidth: .infinity)
                        .accessibilityLabel("Powered by Voltplan")
                }
                .listRowBackground(Color.clear)
                .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
            }
            .textCase(nil)
        }
    }
}

private struct ComponentRow: View {
    let item: ComponentLibraryItem

    var body: some View {
        HStack(spacing: 12) {
            iconView
            VStack(alignment: .leading, spacing: 4) {
                Text(item.localizedName)
                    .font(.headline)
                    .foregroundColor(.primary)
                detailLine
            }
            Spacer()
            Image(systemName: "chevron.right")
                .font(.caption)
                .foregroundColor(Color(.tertiaryLabel))
        }
        .padding(.vertical, 8)
    }

    private var iconView: some View {
        LoadIconView(
            remoteIconURLString: item.iconURL?.absoluteString,
            fallbackSystemName: "bolt",
            fallbackColor: Color.blue.opacity(0.15),
            size: 44
        )
    }

    @ViewBuilder
    private var detailLine: some View {
        let labels = [item.voltageLabel, item.powerLabel, item.currentLabel].compactMap { $0 }

        if labels.isEmpty {
            Text("Details coming soon")
                .font(.caption)
                .foregroundColor(.secondary)
        } else {
            Text(labels.joined(separator: " • "))
                .font(.caption)
                .foregroundColor(.secondary)
        }
    }
}
