"""Apply the Android (Skip Fuse) port recipe to a tutor repo. Text-anchored; asserts on every anchor."""
import sys, os, re, glob, shutil, subprocess
repo, module, appid, label, macapp = sys.argv[1:6]   # e.g. ~/plo-tutor PLOTutorAndroid com.thedigitaladlab.plotutor "PLO Tutor" PLOTutorApp
os.chdir(repo)
pkg = ".".join(re.findall(r'[A-Z][a-z]*|[A-Z]+(?![a-z])', module)).lower()  # PLOTutorAndroid -> plo.tutor.android
print("derived Android package:", pkg)

def edit(path, pairs, must=True):
    s = open(path).read()
    for a, b in pairs:
        if a not in s:
            if must: raise SystemExit(f"ANCHOR MISSING in {path}: {a[:80]!r}")
            else: print("  (skipped, not present)", path, a[:50].replace("\n"," ")); continue
        s = s.replace(a, b)
    open(path, "w").write(s)

# ---------- Package.swift ----------
s = open("Package.swift").read()
if "ANDROID_HOME" not in s:
    s = s.replace("import PackageDescription\n\nlet package = Package(", '''import PackageDescription

// Android. Skip (https://skip.dev) compiles this same Swift for Android and renders the SwiftUI
// through its own stand-in. Everything Android-only below is declared when the Android tooling is
// present — `skip android build` and the Gradle project in `Android/` both set ANDROID_HOME — and
// otherwise not at all: a Mac or iPhone build fetches nothing extra, links nothing extra, and is
// the build it always was.
let android = Context.environment["ANDROID_HOME"] != nil
    || (Context.environment["TARGET_OS_ANDROID"] ?? "0") != "0"

let package = Package(''', 1)
    s = s.replace("    ],\n    targets: [", '''    ],
    dependencies: android ? [
        .package(url: "https://github.com/skiptools/skip-fuse-ui.git", from: "1.0.0"),
        .package(url: "https://github.com/skiptools/skip-fuse.git", from: "1.0.3"),
        .package(url: "https://github.com/skiptools/skip.git", from: "1.9.8"),
    ] : [],
    targets: [''', 1)
    a = '''        .target(name: "PokerStore", dependencies: ["PokerSession"]),'''
    assert a in s
    s = s.replace(a, '''        .target(name: "PokerStore", dependencies: ["PokerSession"]
                    + (android ? [.product(name: "SkipFuse", package: "skip-fuse")] : [])),''')
    a = '''        .target(name: "PokerUI",
                dependencies: ["PokerCore", "PokerAI", "PokerCoach", "PokerSession", "PokerStore"],
                resources: [.process("Resources")]),'''
    assert a in s
    s = s.replace(a, '''        .target(name: "PokerUI",
                dependencies: ["PokerCore", "PokerAI", "PokerCoach", "PokerSession", "PokerStore"]
                    + (android ? [.product(name: "SkipFuseUI", package: "skip-fuse-ui")] : []),
                resources: [.process("Resources")]),''')
    s = s.rstrip("\n") + f'''

// The Android app itself: a shell around PokerUI that Skip's Kotlin side can start. Declared only
// for Android builds so that Xcode and `swift build` on the Mac never see it — see `android` above.
if android {{
    package.products += [
        .library(name: "{module}", type: .dynamic, targets: ["{module}"]),
    ]
    package.targets += [
        .target(name: "{module}",
                dependencies: ["PokerUI", .product(name: "SkipFuseUI", package: "skip-fuse-ui")],
                plugins: [.plugin(name: "skipstone", package: "skip")]),
    ]
}}
'''
    open("Package.swift", "w").write(s)

# ---------- SubscriptionStore ----------
p = "Sources/PokerStore/SubscriptionStore.swift"; s = open(p).read()
if "canImport(StoreKit)" not in s:
    head = "import Foundation\nimport StoreKit\nimport PokerSession\n"
    assert s.startswith(head)
    product = re.search(r'yearlyProductID = "([^"]+)"', s).group(1)
    body = s[len(head):]
    s = "import Foundation\nimport PokerSession\nimport Observation\n#if os(Android)\nimport SkipFuse\n#endif\n\n#if canImport(StoreKit)\nimport StoreKit\n" + body.rstrip("\n") + f'''

#else

/// The store on a device with no App Store — Android, for now.
///
/// The same shape as the Apple one above, so nothing that sits on top of it has to know which
/// device it is on. It never says anybody is subscribed, because it has nobody to ask: Google Play
/// billing is the piece still to be built, and until it exists this class answers "not subscribed"
/// and explains why when somebody tries to buy. `hasCheckedWithApple` keeps its name so the gate
/// above it reads the same on every device; here it means "checked, and there is nothing to check".
@Observable
@MainActor
public final class SubscriptionStore {{

    public static let yearlyProductID = "{product}"

    public enum Status: Equatable, Sendable {{
        case idle
        case working
        case waitingForApproval
        case failed(String)
    }}

    public private(set) var isSubscribed = false
    public private(set) var hasCheckedWithApple = false
    public private(set) var displayPrice: String?
    public private(set) var status: Status = .idle

    /// Called on the main actor after the answer changes. The app uses it to take a paywall
    /// down the moment a purchase lands, wherever it landed.
    @ObservationIgnored public var didChange: (@MainActor () -> Void)?

    public init(productID: String = SubscriptionStore.yearlyProductID) {{}}

    public static func stub(subscribed: Bool, price: String? = nil) -> SubscriptionStore {{
        let store = SubscriptionStore()
        store.isSubscribed = subscribed
        store.hasCheckedWithApple = true
        store.displayPrice = price
        return store
    }}

    public func start() async {{ hasCheckedWithApple = true }}

    public func refreshEntitlement() async {{ hasCheckedWithApple = true }}

    public func purchase() async {{ status = .failed(Copy.notOnThisDevice) }}

    public func restore() async {{ status = .failed(Copy.notOnThisDevice) }}

    public func dismissMessage() {{
        if case .failed = status {{ status = .idle }}
        if case .waitingForApproval = status {{ status = .idle }}
    }}
}}

enum Copy {{
    static let notOnThisDevice =
        "Subscriptions are not available on this device yet."
}}

#endif
'''
    # the Apple class: ObservableObject -> @Observable
    a = "@MainActor\npublic final class SubscriptionStore: ObservableObject {"
    assert s.count(a) == 1
    s = s.replace(a, "@Observable\n@MainActor\npublic final class SubscriptionStore {")
    s = s.replace("    @Published public private(set) var", "    public private(set) var")
    hook = '''    /// Called on the main actor after Apple's answer changes. The app uses it to take a paywall
    /// down the moment a purchase lands, wherever it landed.
    @ObservationIgnored public var didChange: (@MainActor () -> Void)?

'''
    a = "    private let productID: String\n"; assert a in s
    s = s.replace(a, hook + "    @ObservationIgnored private let productID: String\n", 1)
    a = "    private var product: Product?\n    private var listener: Task<Void, Never>?\n"; assert a in s
    s = s.replace(a, "    @ObservationIgnored private var product: Product?\n    @ObservationIgnored private var listener: Task<Void, Never>?\n", 1)
    a = "        isSubscribed = owned\n        hasCheckedWithApple = true\n"; assert a in s
    s = s.replace(a, a + "        didChange?()\n", 1)
    open(p, "w").write(s)

# ---------- AppState ----------
p = "Sources/PokerUI/AppState.swift"; s = open(p).read()
if "@Observable" not in s:
    edit(p, [
        ("import Foundation\nimport Combine\n", "import Foundation\nimport Observation\n#if os(Android)\nimport SkipFuse\n#endif\n"),
        ("@MainActor\npublic final class AppState: ObservableObject {", "@Observable\n@MainActor\npublic final class AppState {"),
        ("        session = AppState.ownSession()\n", "        _session = AppState.ownSession()\n"),
        ("        self.session = session\n", "        self._session = session\n"),
        ("    private var subscriptionWatch: AnyCancellable?\n\n", ""),
        ("""        subscriptionWatch = subscription.objectWillChange.sink { [weak self] _ in
            guard let self else { return }
            Task { @MainActor in
                self.objectWillChange.send()
                self.dismissPaywallIfPaid()
            }
        }
""", """        subscription.didChange = { [weak self] in self?.dismissPaywallIfPaid() }
"""),
        ("    public let session: TableSession\n", """    /// The session is a plain class that SwiftUI cannot watch. Every screen reaches it through
    /// this property, which reads `generation` on the way — so bumping that counter (`redraw()`)
    /// redraws every screen that read anything from the session. That is the same "something
    /// moved" signal `ObservableObject` used to give for free, kept on purpose: the alternative
    /// is teaching the session about SwiftUI, and it is the one part of the app that must not know.
    public var session: TableSession { _ = generation; return _session }
    private let _session: TableSession
    private var generation = 0

    /// Tell the screens the session moved.
    private func redraw() { generation &+= 1 }
"""),
    ])
    s = open(p).read()
    s = re.sub(r"^(\s+)session = staged\n", r"\1_session = staged\n", s, flags=re.M)
    s = s.replace("    @Published ", "    ").replace("objectWillChange.send()", "redraw()")
    open(p, "w").write(s)

# ---------- screens ----------
for f in glob.glob("Sources/PokerUI/*.swift"):
    s = open(f).read(); o = s
    s = s.replace("@EnvironmentObject var app: AppState", "@Environment(AppState.self) var app")
    s = s.replace(".environmentObject(app)", ".environment(app)")
    if s != o: open(f, "w").write(s)
edit("Sources/PokerUI/RootView.swift", [("    public var body: some View {\n        GeometryReader { geo in",
                                          "    public var body: some View {\n        @Bindable var app = app\n        GeometryReader { geo in")], must=False)
for f in [f"Sources/{macapp}/{macapp}.swift"] + glob.glob("App/*/PhoneApp.swift"):
    edit(f, [("@StateObject private var app = AppState()", "@State private var app = AppState()"),
             (".environmentObject(app)", ".environment(app)")], must=False)
edit(f"Sources/{macapp}/{macapp}.swift", [("""        .windowStyle(.hiddenTitleBar)
        .defaultSize(width: 1240, height: 820)
""", """        // Mac-only window furniture. Skip's Android packaging compiles every target for the
        // iPhone first, this one included, and iPhones have no windows to style.
        #if os(macOS)
        .windowStyle(.hiddenTitleBar)
        .defaultSize(width: 1240, height: 820)
        #endif
""")], must=False)

# ---------- Theme: imports + shims ----------
p = "Sources/PokerUI/Theme.swift"; s = open(p).read()
if "hoverHighlight" not in s:
    a = "import Foundation\nimport SwiftUI\n"; assert s.startswith(a)
    s = s.replace(a, """#if !os(Android)
import Foundation
#endif
// On Android, Skip's SwiftUI brings Foundation with it, and importing Foundation here as well
// leaves two `CGFloat`s in scope (Foundation's own and SwiftUI's stand-in) — nearly four hundred
// "ambiguous" errors, all from this one line. SwiftUI re-exports Foundation on Apple's platforms
// too, so nothing is lost either way.
import SwiftUI
""", 1)
    s = s.replace(".onHover { hovering = $0 && enabled }", ".hoverHighlight { hovering = $0 && enabled }")
    s = s.replace(".onHover { hovering = $0 }", ".hoverHighlight { hovering = $0 }")
    s = s.replace(".monospacedDigit()", ".digitsAligned()")
    s = s.rstrip("\n") + '''

// MARK: - Pointer-only niceties

extension View {
    /// Hover feedback where there is a pointer to hover with. A phone has none, and Skip's Android
    /// SwiftUI has no such modifier, so there it is a no-op rather than a build error.
    @ViewBuilder func hoverHighlight(_ perform: @escaping (Bool) -> Void) -> some View {
        #if os(Android)
        self
        #else
        self.onHover(perform: perform)
        #endif
    }

    /// A tooltip, on the platforms that draw them.
    @ViewBuilder func tooltip(_ text: String) -> some View {
        #if os(Android)
        self
        #else
        self.help(text)
        #endif
    }
}

#if os(Android)
extension View {
    /// Skip's SwiftUI has no `contentShape` yet. On Android every button is tappable across its
    /// whole frame already, so this leaves the view as it is rather than failing the build.
    func contentShape<S: Shape>(_ shape: S) -> some View { self }
}
#endif

// MARK: - Modifiers Skip's Android SwiftUI does not have yet
//
// Each of these is the real modifier on Apple's platforms and a no-op on Android, so a screen can
// ask for the nicety once and the build decides. When Skip grows the modifier, delete the shim.

extension View {
    /// `menuStyle(.borderlessButton)`: a menu drawn as its label alone.
    @ViewBuilder func plainMenuStyle() -> some View {
        #if os(Android)
        self
        #else
        self.menuStyle(.borderlessButton)
        #endif
    }

    /// `controlSize(.mini)`: the smallest switch.
    @ViewBuilder func miniControl() -> some View {
        #if os(Android)
        self
        #else
        self.controlSize(.mini)
        #endif
    }

    /// `accessibilityElement(children: .ignore)`: read this view out as one thing.
    @ViewBuilder func oneAccessibleElement() -> some View {
        #if os(Android)
        self
        #else
        self.accessibilityElement(children: .ignore)
        #endif
    }
}

extension Text {
    /// `monospacedDigit()`: figures that line up in columns. Android's rounded face is close enough
    /// until Skip offers the real thing.
    func digitsAligned() -> Text {
        #if os(Android)
        self
        #else
        self.monospacedDigit()
        #endif
    }
}
'''
    open(p, "w").write(s)

# ---------- the sites ----------
for f in glob.glob("Sources/PokerUI/*.swift"):
    if f.endswith("Theme.swift"): continue
    s = open(f).read(); o = s
    s = s.replace(".onHover { pressed = $0 && enabled }", ".hoverHighlight { pressed = $0 && enabled }")
    s = s.replace(".help(term.definition)", ".tooltip(term.definition)")
    s = s.replace(".monospacedDigit()", ".digitsAligned()")
    s = s.replace(".menuStyle(.borderlessButton)", ".plainMenuStyle()")
    s = s.replace(".controlSize(.mini)", ".miniControl()")
    s = s.replace(".accessibilityElement(children: .ignore)", ".oneAccessibleElement()")
    s = s.replace(".accessibilityLabel(accessibilityText)", ".accessibilityLabel(Text(accessibilityText))")
    if s != o: open(f, "w").write(s)

# FlowRow
p = "Sources/PokerUI/LadderView.swift"; s = open(p).read()
if "#if !os(Android)" not in s:
    a = "private struct FlowRow: Layout {"; assert a in s
    s = s.replace(a, "#if !os(Android)\n" + a, 1)
    s = s.rstrip("\n") + '''
#else
/// Android has no `Layout` protocol to write a wrapping row against, so the cast goes into as many
/// columns as fit the width instead. Same chips, same words; they line up in rows.
private struct FlowRow<Content: View>: View {
    var spacing: CGFloat = 6
    @ViewBuilder let content: Content

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 110), spacing: spacing)],
                  alignment: .leading, spacing: spacing) { content }
    }
}
#endif
'''
    open(p, "w").write(s)

# Glossary prose
p = "Sources/PokerUI/GlossaryViews.swift"; s = open(p).read()
if "markedDown" not in s:
    edit(p, [
        ("""            word.link = mention.term.link
            word.foregroundColor = Theme.gold
            word.underlineStyle = Text.LineStyle(pattern: .dot, color: Theme.gold.opacity(0.75))
""", """            word.link = mention.term.link
            #if !os(Android)
            // SwiftUI's own text attributes. Skip's Android text has no equivalent yet, so there
            // the words are links in the usual link colour rather than dotted gold.
            word.foregroundColor = Theme.gold
            word.underlineStyle = Text.LineStyle(pattern: .dot, color: Theme.gold.opacity(0.75))
            #endif
"""),
        ("""    var body: some View {
        Text(marked)
            .body(Theme.coachBase(size))""", """    var body: some View {
        prose
            .body(Theme.coachBase(size))"""),
        ("""    /// The writing, with the first appearance of each word turned into a tap target. Dotted rather
    /// than solid, and in the palette's own gold: visibly offered, not shouted.
    private var marked: AttributedString {""", """    /// The paragraph as a `Text`. Apple's SwiftUI takes attributed text; Skip's Android SwiftUI
    /// cannot yet, but reads Markdown, and a Markdown link is the same tap target.
    private var prose: Text {
        #if os(Android)
        Text(LocalizedStringKey(markedDown))
        #else
        Text(marked)
        #endif
    }

    /// The writing as Markdown: each first mention becomes `[word](link)`, everything else escaped
    /// so that a stray asterisk in the coach's prose cannot turn into emphasis.
    private var markedDown: String {
        func escaped(_ plain: Substring) -> String {
            var out = ""
            for ch in plain {
                if "\\\\*_[]`<>#".contains(ch) { out.append("\\\\") }
                out.append(ch)
            }
            return out
        }
        var out = ""
        var cursor = text.startIndex
        for mention in Glossary.mentions(in: text) {
            out += escaped(text[cursor..<mention.range.lowerBound])
            out += "[" + escaped(text[mention.range]) + "](" + mention.term.link.absoluteString + ")"
            cursor = mention.range.upperBound
        }
        out += escaped(text[cursor...])
        return out
    }

    /// The writing, with the first appearance of each word turned into a tap target. Dotted rather
    /// than solid, and in the palette's own gold: visibly offered, not shouted.
    private var marked: AttributedString {"""),
    ])

# Sound
p = "Sources/PokerUI/Sound.swift"; s = open(p).read()
if "canImport(AVFoundation)" not in s:
    s = s.replace("import Foundation\nimport AVFoundation\n", "import Foundation\n#if canImport(AVFoundation)\nimport AVFoundation\n#endif\n", 1)
    s = s.replace("/// The table sounds.\n", "#if canImport(AVFoundation)\n/// The table sounds.\n", 1)
    s = s.rstrip("\n") + '''
#else
/// No sound on Android yet: the table plays silently. Wiring Skip's audio library is a later step,
/// and until then the setting still exists so the screens and the suite do not have to know.
@MainActor
final class Sound {
    static let shared = Sound()

    enum Cue: String, CaseIterable {
        case chips, fold, knock, pot
    }

    var enabled: Bool {
        didSet { UserDefaults.standard.set(enabled, forKey: Self.defaultsKey) }
    }

    private static let defaultsKey = "PokerTutor.soundEnabled"

    private init() {
        if UserDefaults.standard.object(forKey: Self.defaultsKey) == nil {
            enabled = true
        } else {
            enabled = UserDefaults.standard.bool(forKey: Self.defaultsKey)
        }
    }

    func play(_ cue: Cue) {}
    func play(for applied: TableSession.AppliedAction) {}
}
#endif
'''
    open(p, "w").write(s)

# ---------- Skip's plugin on the screens module, and what it demands ----------
import collections
os.makedirs("Sources/PokerUI/Skip", exist_ok=True)
open("Sources/PokerUI/Skip/skip.yml", "w").write("""# Skip configuration for the screens. A natively-compiled Skip Fuse module: the Swift is compiled
# for Android as it is. The Skip build plugin still has to see this module, because it generates
# the glue that lets Skip's SwiftUI draw each view here — without it every screen renders as
# nothing at all, silently.
skip:
  mode: 'native'
""")
s = open("Package.swift").read()
a = """                    + (android ? [.product(name: "SkipFuseUI", package: "skip-fuse-ui")] : []),
                resources: [.process("Resources")]),"""
if a in s:
    s = s.replace(a, """                    + (android ? [.product(name: "SkipFuseUI", package: "skip-fuse-ui")] : []),
                resources: [.process("Resources")],
                plugins: android ? [.plugin(name: "skipstone", package: "skip")] : []),""")
    open("Package.swift", "w").write(s)
# view state/environment properties and view types must not be private
pat = re.compile(r'(@(?:State|Binding|FocusState|Bindable|Namespace|GestureState|ScaledMetric|AppStorage\([^)]*\)|Environment\([^)]*\)|SceneStorage\([^)]*\)))\s+(?:private|fileprivate)\s+(var|let)\b')
for f in glob.glob("Sources/PokerUI/*.swift"):
    t = open(f).read(); t2, k = pat.subn(r'\1 \2', t)
    if k: open(f, "w").write(t2)
pat = re.compile(r'^(\s*)(?:private|fileprivate)\s+(struct\s+(\w+)(?:<[^>\n]*>)?\s*:\s*View\b)', re.M)
names = collections.defaultdict(list)
for f in glob.glob("Sources/PokerUI/*.swift"):
    for m in pat.finditer(open(f).read()): names[m.group(3)].append(f)
dups = {k: v for k, v in names.items() if len(set(v)) > 1}
if dups: raise SystemExit(f"rename these private views first, they clash once internal: {dups}")
for f in glob.glob("Sources/PokerUI/*.swift"):
    t = open(f).read(); t2, k = pat.subn(r'\1\2', t)
    if k: open(f, "w").write(t2)
# public signatures cannot mention the bare `UserDefaults` (the plugin shadows it with a private alias)
p = "Sources/PokerUI/Theme.swift"; t = open(p).read()
for typ, fallback in (("CardColours", ".four"), ("TextScale", ".normal")):
    a = f"public static func stored(in defaults: UserDefaults = .standard) -> {typ} {{"
    if a in t:
        t = t.replace(a, f"public static func stored(in defaults: Foundation.UserDefaults) -> {typ} {{")
        t = re.sub(rf"public static func store\(_ (\w+): {typ}, in defaults: UserDefaults = \.standard\) \{{\n        defaults\.set\(\1\.rawValue, forKey: storageKey\)\n    \}}",
                   lambda m: f"public static func store(_ {m.group(1)}: {typ}, in defaults: Foundation.UserDefaults) {{\n        defaults.set({m.group(1)}.rawValue, forKey: storageKey)\n    }}\n\n    static func stored() -> {typ} {{ stored(in: UserDefaults.standard) }}\n    static func store(_ {m.group(1)}: {typ}) {{ store({m.group(1)}, in: UserDefaults.standard) }}", t)
open(p, "w").write(t)
print("plugin + visibility + settings applied")

# ---------- Android shell ----------
os.makedirs(f"Sources/{module}/Skip", exist_ok=True)
open(f"Sources/{module}/Skip/skip.yml", "w").write("""# Skip configuration for the Android app module. This is a natively-compiled Skip Fuse module:
# the Swift is compiled for Android as it is, and only this thin shell is bridged to Kotlin.
skip:
  mode: 'native'
""")
open(f"Sources/{module}/{module}.swift", "w").write(f'''import Foundation
import SkipFuse
import SwiftUI
import PokerUI

/// The Android app. Same `RootView` as the Mac and the iPhone, which lays itself out for the space
/// it gets — this file is the whole of the platform-specific Swift, and `Android/` holds the
/// equally thin Kotlin that Android needs to start an app at all.
///
/// The `SKIP @bridge` marks are what let that Kotlin reach this Swift. Nothing else in the app is
/// bridged: every screen is compiled Swift talking to Skip's SwiftUI, which draws it with Compose.
/* SKIP @bridge */public struct {module}RootView : View {{
    // Internal rather than private: Skip's bridge has to be able to see it.
    @State var app = AppState()

    /* SKIP @bridge */public init() {{
    }}

    public var body: some View {{
        RootView()
            .environment(app)
            .preferredColorScheme(.dark)
            // The free allowance and the store. Only the shipping apps switch this on: the suite
            // and the command line play far more hands than anybody pays for.
            .task {{ app.startCharging() }}
    }}
}}

/// What Android tells the app about its own life. Nothing listens yet; the table pauses itself
/// when the screen changes and the history is written a hand at a time, so there is nothing to
/// save in a hurry when the app goes to the background.
/* SKIP @bridge */public final class {module}AppDelegate : Sendable {{
    /* SKIP @bridge */public static let shared = {module}AppDelegate()

    private init() {{
    }}

    /* SKIP @bridge */public func onInit() {{}}
    /* SKIP @bridge */public func onLaunch() {{}}
    /* SKIP @bridge */public func onResume() {{}}
    /* SKIP @bridge */public func onPause() {{}}
    /* SKIP @bridge */public func onStop() {{}}
    /* SKIP @bridge */public func onDestroy() {{}}
    /* SKIP @bridge */public func onLowMemory() {{}}
}}
''')
open("Skip.env", "w").write(f"""// The configuration file for the Android app (https://skip.dev).
// Read by Android/settings.gradle.kts and folded into the AndroidManifest.xml.

// PRODUCT_NAME must match the app's Swift module name (Sources/{module}).
PRODUCT_NAME = {module}

// The unique id of the app. The same as the iPhone app's bundle identifier.
PRODUCT_BUNDLE_IDENTIFIER = {appid}

// The version people see, and the build number behind it. Kept in step with the iPhone app.
MARKETING_VERSION = 1.0
CURRENT_PROJECT_VERSION = 1

// The Kotlin package for the Android entry point. Skip derives it from the Swift module name
// ({module} -> {pkg}) and the two MUST agree, or the app cannot find its module.
ANDROID_PACKAGE_NAME = {pkg}
""")
if not os.path.isdir("Android"):
    subprocess.run(["rsync", "-a", "--exclude", "build", "--exclude", ".gradle", os.path.expanduser("~/poker-tutor/Android/"), "Android/"], check=True)
    for f in ["Android/app/src/main/kotlin/Main.kt", "Android/app/src/main/AndroidManifest.xml", "Android/fastlane/Appfile", "Android/fastlane/Fastfile", "Android/fastlane/metadata/android/en-US/title.txt"]:
        if not os.path.exists(f): continue
        s = open(f).read()
        s = s.replace("poker.tutor.android", pkg).replace("PokerTutorAndroid", module)
        s = s.replace("com.thedigitaladlab.pokertutor", appid).replace('category = "PokerTutor"', f'category = "{label.replace(" ", "")}"')
        s = s.replace('android:label="Poker Tutor"', f'android:label="{label}"').replace("Poker Tutor", label)
        open(f, "w").write(s)
print("recipe applied to", repo)
