#!/usr/bin/xcrun --sdk macosx swift

// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable

import Foundation

// Get the Derived Data path and the project's name
let derivedDataPath = getDerivedDataPath() ?? ""
let projectName = ProcessInfo.processInfo.environment["PROJECT_NAME"] ?? ""
let projectPath = ProcessInfo.processInfo.environment["PROJECT_DIR"] ?? FileManager.default.currentDirectoryPath

let packageResolutionFilePath = "\(projectPath)/\(projectName).xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"
let packageCheckoutsPath = "\(derivedDataPath)/SourcePackages/checkouts/"
let packageArtifactsPath = "\(derivedDataPath)/SourcePackages/artifacts/"

func getDerivedDataPath() -> String? {
    // Define the regular expression pattern to extract the DerivedData path
    let regexPattern = ".*DerivedData/[^/]*"
    guard
        let buildDir = ProcessInfo.processInfo.environment["BUILD_DIR"],
        let regex = try? NSRegularExpression(pattern: regexPattern)
    else { return nil }
    
    let range = NSRange(location: 0, length: buildDir.utf16.count)
    
    // Perform the regex matching
    if let match = regex.firstMatch(in: buildDir, options: [], range: range) {
        // Extract the matching portion (the DerivedData path)
        if let range = Range(match.range, in: buildDir) {
            return String(buildDir[range])
        }
    } else {
        print("No DerivedData path found in BUILD_DIR")
    }
    
    return nil
}

// Function to list all directories (Swift package checkouts) inside the SourcePackages/checkouts directory
func listDirectories(atPath path: String) -> [String] {
    let fileManager = FileManager.default
    do {
        let items = try fileManager.contentsOfDirectory(atPath: path)
        return items.filter { item in
            var isDir: ObjCBool = false
            let fullPath = path + "/" + item
            return fileManager.fileExists(atPath: fullPath, isDirectory: &isDir) && isDir.boolValue
        }
    } catch {
        print("Error reading contents of directory: \(error)")
        return []
    }
}

// Function to find and read LICENSE files in each package
func findLicenses(in packagesPath: String) -> [(package: String, licenseContent: String)] {
    var licenses: [(package: String, licenseContent: String)] = []
    let packages: [String] = listDirectories(atPath: packagesPath)
    
    print("\(packages.count) packages found in \(packagesPath)")

    packages.forEach { package in
        let packagePath = "\(packagesPath)/\(package)"
        scanDirectory(atPath: packagePath) { filePath in
            if filePath.lowercased().contains("license") || filePath.lowercased().contains("copying") {
                if let licenseContent = try? String(contentsOfFile: filePath, encoding: .utf8) {
                    licenses.append((package, licenseContent))
                }
            }
        }
    }

    return licenses
}

func findPackageDependencyNames(in resolutionFilePath: String) throws -> Set<String> {
    struct ResolvedPackages: Codable {
        struct Pin: Codable {
            struct State: Codable {
                let revision: String
                let version: String
            }
            
            let identity: String
            let kind: String
            let location: String
            let state: State
        }
        
        let originHash: String
        let pins: [Pin]
        let version: Int
    }
    
    do {
        let data: Data = try Data(contentsOf: URL(fileURLWithPath: resolutionFilePath))
        let resolvedPackages: ResolvedPackages = try JSONDecoder().decode(ResolvedPackages.self, from: data)
        
        print("Found \(resolvedPackages.pins.count) resolved packages.")
        return Set(resolvedPackages.pins.map { $0.identity.lowercased() })
    }
    catch {
        print("error: Failed to load list of resolved packages")
        throw error
    }
}

func scanDirectory(atPath path: String, foundFile: (String) -> Void) {
    if let enumerator = FileManager.default.enumerator(atPath: path) {
        for case let file as String in enumerator {
            let fullPath = "\(path)/\(file)"
            if FileManager.default.fileExists(atPath: fullPath, isDirectory: nil) {
                foundFile(fullPath)
            }
        }
    }
}

// Write licenses to a plist file
func writePlist(licenses: [(package: String, licenseContent: String)], resolvedPackageNames: Set<String>, outputPath: String) {
    var plistArray: [[String: String]] = []
    let finalLicenses: [(package: String, licenseContent: String)] = licenses
        .filter { resolvedPackageNames.contains($0.package.lowercased()) }
        .sorted(by: { $0.package.lowercased() < $1.package.lowercased() })
    
    print("\(finalLicenses.count) being written to plist.")
    
    finalLicenses.forEach { license in
        plistArray.append([
            "Title": license.package,
            "License": license.licenseContent
        ])
    }
    
    let plistData = try! PropertyListSerialization.data(fromPropertyList: plistArray, format: .xml, options: 0)
    let plistURL = URL(fileURLWithPath: outputPath)
    try? plistData.write(to: plistURL)
}

// Execute the license discovery process
let licenses = findLicenses(in: packageCheckoutsPath) + findLicenses(in: packageArtifactsPath)
let resolvedPackageNames = try findPackageDependencyNames(in: packageResolutionFilePath)

// Specify the path for the output plist
let outputPlistPath = "\(projectPath)/\(projectName)/Meta/Settings.bundle/ThirdPartyLicenses.plist"
writePlist(licenses: licenses, resolvedPackageNames: resolvedPackageNames, outputPath: outputPlistPath)

print("Licenses generated successfully at \(outputPlistPath)")
