//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if canImport(CollectionsInternal)
internal import CollectionsInternal
#elseif canImport(OrderedCollections)
internal import OrderedCollections
#elseif canImport(_FoundationCollections)
internal import _FoundationCollections
#endif

extension URL {
    /// Creates a new `URL` by expanding the RFC 6570 template and variables.
    ///
    /// This will fail if variable expansion does not produce a valid,
    /// well-formed URL.
    ///
    /// All text will be converted to NFC (Unicode Normalization Form C) and UTF-8
    /// before being percent-encoded if needed.
    ///
    /// - Parameters:
    ///   - template: The RFC 6570 template to be expanded.
    ///   - variables: Variables to expand in the template.
    @available(FoundationPreview 6.2, *)
    public init?(
        template: URL.Template,
        variables: [URL.Template.VariableName: URL.Template.Value]
    ) {
        self.init(string: template.expand(variables))
    }
}

extension URL.Template {
    /// Expands the expressions in the template and returns the resulting URI as a ``Swift/String``.
    func expand(_ variables: [VariableName: Value]) -> String {
        replaceVariables(variables.mapValues({ $0.underlying }))
    }

    func replaceVariables(_ variables: [VariableName: Value.Underlying]) -> String {
        return elements.reduce(into: "") { result, element in
            switch element {
            case .literal(let literal):
                result.append(literal)
            case .expression(let expression):
                result += expression.replacement(variables)
            }
        }
    }
}

// MARK: -

extension URL.Template.Expression {
    fileprivate func replacement(_ variables: [URL.Template.VariableName: URL.Template.Value.Underlying]) -> String {
        let escapedValues: [(String?, String)] = elements.flatMap {
            $0.escapedValues(
                operator: `operator`,
                variables: variables
            )
        }

        return escapedValues.enumerated().reduce(into: "") { result, element in
            let isFirst = element.offset == 0
            let name = element.element.0
            let value = element.element.1

            if isFirst {
                if let c = `operator`.firstPrefix {
                    result.append(c)
                }
            } else {
                result.append(`operator`.separator)
            }
            if let name {
                result.append(name)
                if value.isEmpty {
                    if let c = `operator`.replacementForEmpty {
                        result.append(c)
                    }
                } else {
                    result.append("=")
                    result.append(value)
                }
            } else {
                result.append(value)
            }
        }
    }
}

extension URL.Template.Expression.Element {
    fileprivate func escapedValues(
        `operator`: URL.Template.Expression.Operator?,
        variables: [URL.Template.VariableName: URL.Template.Value.Underlying]
    ) -> [(String?, String)] {
        func makeNormalized(_ value: String) -> String {
            let v: String = maximumLength.map { String(value.prefix($0)) } ?? value
            return v.normalizedAddingPercentEncoding(
                withAllowedCharacters: `operator`.allowedCharacters
            )
        }

        func makeElement(_ value: String) -> (String?, String) {
            return (
                `operator`.isNamed ? String(name) : nil,
                makeNormalized(value)
            )
        }

        func makeElement(_ values: [String]) -> (String?, String) {
            return (
                `operator`.isNamed ? String(name) : nil,
                values
                    .map(makeNormalized)
                    .joined(separator: ",")
            )
        }

        switch variables[name] {
        case .text(let s):
            return [makeElement(s)]
        case .list(let a):
            if explode {
                return a.map { makeElement($0) }
            } else {
                return [makeElement(a)]
            }
        case .associativeList(let d):
            if explode {
                return d.lazy.map {
                    (
                        makeNormalized($0.key),
                        makeNormalized($0.value)
                    )
                }
            } else if d.isEmpty {
                return []
            } else {
                return [
                    makeElement(d.lazy.flatMap { [$0.key, $0.value] }),
                ]
            }
        default:
            return []
        }
    }
}
