import ProjectDescription
import ProjectDescriptionHelpers

// MARK: - Constants

configureOpenSwiftUIEnvironment()
let exampleServerConfiguration = OpenSwiftUIExampleServerConfiguration.resolve()
let enableLookInsideServer = exampleServerConfiguration.enableLookInsideServer
let enableLookInServer = exampleServerConfiguration.enableLookInServer

let destinations: Destinations = [.iPhone, .iPad, .mac, .appleVision]

let deploymentTargets = DeploymentTargets.multiplatform(
    iOS: "18.0",
    macOS: "15.0",
    visionOS: "2.0"
)

let openSwiftUIDebug = ConfigurationName.configuration("OpenSwiftUIDebug")
let openSwiftUIRelease = ConfigurationName.configuration("OpenSwiftUIRelease")
let swiftUIDebug = ConfigurationName.configuration("SwiftUIDebug")
let swiftUIRelease = ConfigurationName.configuration("SwiftUIRelease")

let projectConfigurations: [Configuration] = [
    .debug(name: swiftUIDebug, xcconfig: "../Configurations/Shared/SwiftUI-debug.xcconfig"),
    .release(name: swiftUIRelease, xcconfig: "../Configurations/Shared/SwiftUI-release.xcconfig"),
    .debug(name: openSwiftUIDebug, xcconfig: "../Configurations/Shared/OpenSwiftUI-debug.xcconfig"),
    .release(name: openSwiftUIRelease, xcconfig: "../Configurations/Shared/OpenSwiftUI-release.xcconfig"),
]

let swiftUIModeSettings: SettingsDictionary = [
    "OPENSWIFTUI_TARGET_BUNDLE_ID": "SwiftUI",
    "SWIFT_VERSION": "5.0",
]

let openSwiftUIModeSettings: SettingsDictionary = [
    "GCC_PREPROCESSOR_DEFINITIONS": "$(inherited) OPENSWIFTUI=1",
    "OPENSWIFTUI_TARGET_BUNDLE_ID": "OpenSwiftUI",
    "SWIFT_ACTIVE_COMPILATION_CONDITIONS": "$(inherited) OPENSWIFTUI",
    "SWIFT_VERSION": "5.0",
]

let debugModeSettings: SettingsDictionary = [
    "ENABLE_TESTABILITY": "YES",
    "SWIFT_COMPILATION_MODE": "singlefile",
    "SWIFT_OPTIMIZATION_LEVEL": "-Onone",
]

let openSwiftUIDebugModeSettings = openSwiftUIModeSettings.merging([
    "ENABLE_TESTABILITY": "YES",
    "SWIFT_COMPILATION_MODE": "singlefile",
    "SWIFT_OPTIMIZATION_LEVEL": "-Onone",
])

let inspectUIServerReleaseExclusionSettings: SettingsDictionary = (enableLookInsideServer || enableLookInServer)
    ? [
        "EXCLUDED_SOURCE_FILE_NAMES": "$(inherited) LookInsideServer* LookinServer*",
    ]
    : [:]

func targetConfigurations(_ xcconfig: Path) -> [Configuration] {
    [
        .debug(name: swiftUIDebug, settings: swiftUIModeSettings.merging(debugModeSettings), xcconfig: xcconfig),
        .release(name: swiftUIRelease, settings: swiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig),
        .debug(name: openSwiftUIDebug, settings: openSwiftUIDebugModeSettings, xcconfig: xcconfig),
        .release(name: openSwiftUIRelease, settings: openSwiftUIModeSettings.merging(inspectUIServerReleaseExclusionSettings), xcconfig: xcconfig),
    ]
}

func settings(base: SettingsDictionary = [:], xcconfig: Path) -> Settings {
    .settings(
        base: base,
        configurations: targetConfigurations(xcconfig),
        defaultSettings: .essential,
        defaultConfiguration: "OpenSwiftUIDebug"
    )
}

let commonAppSettings: SettingsDictionary = [
    "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon",
    "ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME": "AccentColor",
    "CODE_SIGN_STYLE": "Automatic",
    "DEVELOPMENT_TEAM": "",
    "ENABLE_PREVIEWS": "YES",
    "GENERATE_INFOPLIST_FILE": "YES",
    "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]": "YES",
    "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]": "YES",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]": "YES",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]": "YES",
    "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]": "YES",
    "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]": "YES",
    "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]": "UIStatusBarStyleDefault",
    "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]": "UIStatusBarStyleDefault",
    "INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad": "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight",
    "INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone": "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight",
    "FRAMEWORK_SEARCH_PATHS": [
        "$(inherited)",
        "$(BUILD_DIR)/Debug$(EFFECTIVE_PLATFORM_NAME)",
        "$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)",
    ],
    "LD_RUNPATH_SEARCH_PATHS": [
        "@executable_path/Frameworks",
    ],
    "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]": "@executable_path/../Frameworks",
    "LIBRARY_SEARCH_PATHS": [
        "$(inherited)",
        "$(BUILD_DIR)/Debug$(EFFECTIVE_PLATFORM_NAME)",
        "$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)",
    ],
    "SDKROOT": "auto",
    "SUPPORTED_PLATFORMS": "iphoneos iphonesimulator macosx xros xrsimulator",
    "SUPPORTS_MACCATALYST": "NO",
    "SWIFT_EMIT_LOC_STRINGS": "YES",
    "SWIFT_INCLUDE_PATHS": [
        "$(inherited)",
        "$(SRCROOT)/Modules/Platform/cocoa",
    ],
    "SWIFT_VERSION": "5.0",
    "TARGETED_DEVICE_FAMILY": "1,2,7",
]

let exampleSettings = commonAppSettings.merging([
    "ENABLE_HARDENED_RUNTIME": "YES",
])

let hostingExampleSettings = commonAppSettings.merging([
    "CLANG_ENABLE_MODULES": "YES",
    "GENERATE_INFOPLIST_FILE": "YES",
    "INFOPLIST_FILE": "HostingExample/Info.plist",
    "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]": "NO",
    "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]": "NO",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]": "YES",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]": "YES",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=xros*]": "YES",
    "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=xrsimulator*]": "YES",
    "INFOPLIST_KEY_UILaunchStoryboardName[sdk=iphoneos*]": "LaunchScreen",
    "INFOPLIST_KEY_UILaunchStoryboardName[sdk=iphonesimulator*]": "LaunchScreen",
    "INFOPLIST_KEY_UILaunchStoryboardName[sdk=xros*]": "LaunchScreen",
    "INFOPLIST_KEY_UILaunchStoryboardName[sdk=xrsimulator*]": "LaunchScreen",
    "INFOPLIST_KEY_UIMainStoryboardFile[sdk=iphoneos*]": "Main",
    "INFOPLIST_KEY_UIMainStoryboardFile[sdk=iphonesimulator*]": "Main",
    "INFOPLIST_KEY_UIMainStoryboardFile[sdk=xros*]": "Main",
    "INFOPLIST_KEY_UIMainStoryboardFile[sdk=xrsimulator*]": "Main",
    "LD_RUNPATH_SEARCH_PATHS": [
        "$(inherited)",
        "@executable_path/Frameworks",
    ],
    "REGISTER_APP_GROUPS": "NO",
    "SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD": "NO",
    "SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD": "NO",
    "SWIFT_OPTIMIZATION_LEVEL": "-Onone",
    "XROS_DEPLOYMENT_TARGET": "2.0",
])

let testingHostSettings = commonAppSettings.merging([
    "REGISTER_APP_GROUPS": "YES",
])

let uiTestsSettings: SettingsDictionary = [
    "BUNDLE_LOADER": "$(TEST_HOST)",
    "CODE_SIGN_STYLE": "Automatic",
    "FRAMEWORK_SEARCH_PATHS": [
        "$(inherited)",
        "$(BUILD_DIR)/Debug$(EFFECTIVE_PLATFORM_NAME)",
        "$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)",
    ],
    "GENERATE_INFOPLIST_FILE": "YES",
    "LIBRARY_SEARCH_PATHS": [
        "$(inherited)",
        "$(BUILD_DIR)/Debug$(EFFECTIVE_PLATFORM_NAME)",
        "$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)",
    ],
    "LOCALIZATION_PREFERS_STRING_CATALOGS": "YES",
    "SDKROOT": "auto",
    "SUPPORTED_PLATFORMS": "iphoneos iphonesimulator macosx xros xrsimulator",
    "SUPPORTS_MACCATALYST": "NO",
    "SWIFT_EMIT_LOC_STRINGS": "NO",
    "SWIFT_INCLUDE_PATHS": [
        "$(inherited)",
        "$(SRCROOT)/Modules/Platform/cocoa",
    ],
    "SWIFT_OBJC_BRIDGING_HEADER": "OpenSwiftUIUITests/OpenSwiftUIUITests-Bridging-Header.h",
    "SWIFT_VERSION": "5.0",
    "TARGETED_DEVICE_FAMILY": "1,2,7",
    "TEST_HOST": "$(BUILT_PRODUCTS_DIR)/TestingHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestingHost",
]

let sharedSources: SourceFilesList = [
    "Shared/**/*.swift",
]

let interFontInfoPlist: [String: Plist.Value] = [
    "UIAppFonts": .array([
        .string("InterVariable.ttf"),
        .string("InterVariable-Italic.ttf"),
    ]),
]

let sharedResources: ResourceFileElements = [
    "Shared/Assets/Assets.xcassets",
    "Shared/Assets/fonts/**",
    "Shared/Assets/images/**",
]

let privateFrameworkDependencies: [TargetDependency] = [
    .external(name: "AttributeGraph"),
    .external(name: "RenderBox"),
    .external(name: "CoreUI"),
    .external(name: "CoreSVG"),
    .external(name: "SFSymbols"),
    .external(name: "BacklightServices", condition: .when([.ios, .visionos])),
]

let debugServerDependencies: [TargetDependency]
if enableLookInsideServer {
    debugServerDependencies = [
        .external(name: "LookInsideServer", condition: .when([.ios, .macos])),
    ]
} else if enableLookInServer {
    debugServerDependencies = [
        .external(name: "LookinServer", condition: .when([.ios])),
    ]
} else {
    debugServerDependencies = []
}

let appDependencies: [TargetDependency] = [
    .external(name: "OpenSwiftUI"),
    .external(name: "Equatable"),
] + privateFrameworkDependencies + debugServerDependencies

let testArguments = Arguments.arguments(
    environmentVariables: [
        "SWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
    ]
)

let launchArguments = Arguments.arguments(
    environmentVariables: [
        "SWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
    ]
)

let openSwiftUILaunchArguments = Arguments.arguments(
    environmentVariables: [
        "OPENSWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
        "SWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
    ]
)

let openSwiftUIHostingLaunchArguments = Arguments.arguments(
    environmentVariables: [
        "SWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
        "OPENSWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
    ]
)

let swiftUIHostingLaunchArguments = Arguments.arguments(
    environmentVariables: [
        "SWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
        "AG_PRINT_CYCLES": .environmentVariable(value: "2", isEnabled: false),
        "AG_TRAP_CYCLES": .environmentVariable(value: "1", isEnabled: false),
        "OPENSWIFTUI_PRINT_TREE": .environmentVariable(value: "1", isEnabled: false),
    ]
)

// MARK: - Targets

let targets: [Target] = [
    .target(
        name: "Example",
        destinations: destinations,
        product: .app,
        bundleId: "org.OpenSwiftUIProject.$(OPENSWIFTUI_TARGET_BUNDLE_ID).Example",
        deploymentTargets: deploymentTargets,
        infoPlist: .extendingDefault(with: interFontInfoPlist),
        sources: [
            "Example/**/*.swift",
            "Shared/**/*.swift",
        ],
        resources: sharedResources,
        entitlements: "Example/Example.entitlements",
        dependencies: appDependencies,
        settings: settings(base: exampleSettings, xcconfig: "../Configurations/Example.xcconfig")
    ),
    .target(
        name: "HostingExample",
        destinations: destinations,
        product: .app,
        bundleId: "org.OpenSwiftUIProject.$(OPENSWIFTUI_TARGET_BUNDLE_ID).HostingExample",
        deploymentTargets: deploymentTargets,
        infoPlist: "HostingExample/Info.plist",
        sources: [
            .glob("HostingExample/**/*.swift"),
            .glob("Shared/**/*.swift"),
        ],
        resources: [
            .glob(pattern: "HostingExample/Base.lproj/**", inclusionCondition: .when([.ios, .visionos])),
            "Shared/Assets/Assets.xcassets",
            "Shared/Assets/fonts/**",
            "Shared/Assets/images/**",
        ],
        dependencies: appDependencies,
        settings: settings(base: hostingExampleSettings, xcconfig: "../Configurations/HostingExample.xcconfig")
    ),
    .target(
        name: "TestingHost",
        destinations: destinations,
        product: .app,
        bundleId: "org.OpenSwiftUIProject.$(OPENSWIFTUI_TARGET_BUNDLE_ID).TestingHost",
        deploymentTargets: deploymentTargets,
        infoPlist: .extendingDefault(with: interFontInfoPlist),
        sources: [
            "TestingHost/**/*.swift",
            "Shared/**/*.swift",
        ],
        resources: sharedResources,
        entitlements: "TestingHost/TestingHost.entitlements",
        dependencies: appDependencies,
        settings: settings(base: testingHostSettings, xcconfig: "../Configurations/TestingHost.xcconfig")
    ),
    .target(
        name: "OpenSwiftUIUITests",
        destinations: destinations,
        product: .unitTests,
        bundleId: "org.OpenSwiftUIProject.$(OPENSWIFTUI_TARGET_BUNDLE_ID).OpenSwiftUIUITests",
        deploymentTargets: deploymentTargets,
        infoPlist: .default,
        sources: [
            "OpenSwiftUIUITests/**/*.swift",
            "OpenSwiftUIUITests/**/*.m",
            "OpenSwiftUIUITests/**/*.c",
        ],
        dependencies: [
            .target(name: "TestingHost"),
            .external(name: "SnapshotTesting"),
            .external(name: "Equatable"),
        ] + privateFrameworkDependencies,
        settings: settings(base: uiTestsSettings, xcconfig: "../Configurations/OpenSwiftUIUITests.xcconfig")
    ),
]

// MARK: - Schemes

let swiftUIDisplayListLLDBInitFile: Path =
    "../Scripts/LLDB/.lldbinit-swiftui"

func scheme(
    name: String,
    target: TargetReference,
    debugConfiguration: ConfigurationName,
    releaseConfiguration: ConfigurationName,
    testableTargets: [TestableTarget] = [
        .testableTarget(target: "OpenSwiftUIUITests", parallelization: .enabled),
    ],
    includeTestAction: Bool = true,
    runArguments: Arguments = launchArguments,
    customLLDBInitFile: Path? = nil
) -> Scheme {
    .scheme(
        name: name,
        shared: true,
        buildAction: .buildAction(targets: [target]),
        testAction: includeTestAction
            ? .targets(
                testableTargets,
                arguments: testArguments,
                configuration: debugConfiguration,
                expandVariableFromTarget: "TestingHost"
            )
            : nil,
        runAction: .runAction(
            configuration: debugConfiguration,
            customLLDBInitFile: customLLDBInitFile,
            executable: .executable(target),
            arguments: runArguments
        ),
        archiveAction: .archiveAction(configuration: releaseConfiguration),
        profileAction: .profileAction(
            configuration: releaseConfiguration,
            executable: .executable(target)
        ),
        analyzeAction: .analyzeAction(configuration: debugConfiguration)
    )
}

let schemes: [Scheme] = [
    scheme(
        name: "OSUI_Example",
        target: "Example",
        debugConfiguration: openSwiftUIDebug,
        releaseConfiguration: openSwiftUIRelease,
        runArguments: openSwiftUILaunchArguments
    ),
    scheme(
        name: "SUI_Example",
        target: "Example",
        debugConfiguration: swiftUIDebug,
        releaseConfiguration: swiftUIRelease,
        customLLDBInitFile: swiftUIDisplayListLLDBInitFile
    ),
    scheme(
        name: "OSUI_HostingExample",
        target: "HostingExample",
        debugConfiguration: openSwiftUIDebug,
        releaseConfiguration: openSwiftUIRelease,
        testableTargets: [],
        runArguments: openSwiftUIHostingLaunchArguments
    ),
    scheme(
        name: "SUI_HostingExample",
        target: "HostingExample",
        debugConfiguration: swiftUIDebug,
        releaseConfiguration: swiftUIRelease,
        testableTargets: [],
        runArguments: swiftUIHostingLaunchArguments,
        customLLDBInitFile: swiftUIDisplayListLLDBInitFile
    ),
    scheme(
        name: "OSUI_TestingHost",
        target: "TestingHost",
        debugConfiguration: openSwiftUIDebug,
        releaseConfiguration: openSwiftUIRelease,
        includeTestAction: false
    ),
    scheme(
        name: "SUI_TestingHost",
        target: "TestingHost",
        debugConfiguration: swiftUIDebug,
        releaseConfiguration: swiftUIRelease,
        includeTestAction: false,
        customLLDBInitFile: swiftUIDisplayListLLDBInitFile
    ),
    .scheme(
        name: "OSUI_UITests",
        shared: true,
        buildAction: .buildAction(targets: ["OpenSwiftUIUITests"]),
        testAction: .targets(
            [.testableTarget(target: "OpenSwiftUIUITests", parallelization: .enabled)],
            arguments: testArguments,
            configuration: openSwiftUIDebug,
            expandVariableFromTarget: "TestingHost"
        ),
        analyzeAction: .analyzeAction(configuration: openSwiftUIDebug)
    ),
    .scheme(
        name: "SUI_UITests",
        shared: true,
        buildAction: .buildAction(targets: ["OpenSwiftUIUITests"]),
        testAction: .targets(
            [.testableTarget(target: "OpenSwiftUIUITests", parallelization: .enabled)],
            arguments: testArguments,
            configuration: swiftUIDebug,
            expandVariableFromTarget: "TestingHost"
        ),
        analyzeAction: .analyzeAction(configuration: swiftUIDebug)
    ),
]

// MARK: - Project

let project = Project(
    name: "Example",
    options: .options(
        automaticSchemesOptions: .disabled,
        developmentRegion: "en"
    ),
    settings: .settings(
        configurations: projectConfigurations,
        defaultSettings: .essential,
        defaultConfiguration: "OpenSwiftUIDebug"
    ),
    targets: targets,
    schemes: schemes,
    additionalFiles: [
        "../Configurations/OpenSwiftUI-Info.plist",
        "../Configurations/Shared/basic/**",
        "Modules/**",
        "ReferenceImages/**",
        "OpenSwiftUIUITests/OpenSwiftUIUITests.xctestplan",
    ]
)
