// el-publish — App Store + Play Store publishing pipeline.
//
// Two providers (Apple, Google), one publish flow:
//   1. Validate config + certs
//   2. Build artifact (delegated to el-ui-compiler / xcodebuild / gradle)
//   3. Capture metadata + screenshots
//   4. Upload to App Store Connect / Play Developer API
//   5. Monitor rollout

// ── Errors ───────────────────────────────────────────────────────────────────

let PUB_ERR_CONFIG:      String = "publish.config"
let PUB_ERR_BUILD:       String = "publish.build"
let PUB_ERR_UPLOAD:      String = "publish.upload"
let PUB_ERR_CERT:        String = "publish.certificate"
let PUB_ERR_METADATA:    String = "publish.metadata"
let PUB_ERR_API:         String = "publish.api"
let PUB_ERR_IO:          String = "publish.io"

// ── Tracks ───────────────────────────────────────────────────────────────────

let TRACK_PRODUCTION: String = "production"
let TRACK_BETA:       String = "beta"        // TestFlight on Apple; "beta" on Google
let TRACK_INTERNAL:   String = "internal"
let TRACK_ALPHA:      String = "alpha"

// ── Apple config ─────────────────────────────────────────────────────────────

type AppleConfig {
    account:    String   // Apple ID
    bundle_id:  String   // ai.neurontechnologies.myapp
    team_id:    String
    api_key_id: String   // App Store Connect API key
    api_issuer: String
}

// ── Google config ────────────────────────────────────────────────────────────

type GoogleConfig {
    package:                  String   // ai.neurontechnologies.myapp
    track:                    String   // production | beta | internal | alpha
    service_account_json_ref: String   // secret ref to service-account JSON
}

// ── Rollout (staged) ─────────────────────────────────────────────────────────

type RolloutConfig {
    initial_percent: Int   // 0..100
    target_percent:  Int
    bake_hours:      Int   // hours between rollout stages
}

fn rollout_default() -> RolloutConfig {
    { "initial_percent": 5, "target_percent": 100, "bake_hours": 24 }
}

// ── Top-level publish config ────────────────────────────────────────────────

type PublishConfig {
    version:       String
    build_number:  Int
    apple_json:    String   // empty if not configured
    google_json:   String
    rollout_json:  String   // RolloutConfig as JSON
}

fn publish_config_new(version: String, build_number: Int) -> PublishConfig {
    { "version": version, "build_number": build_number,
      "apple_json": "", "google_json": "", "rollout_json": json_encode(rollout_default()) }
}

// ── Outcome ──────────────────────────────────────────────────────────────────

type PublishOutcome {
    platform:        String
    version:         String
    build_number:    Int
    track:           String
    rollout_percent: Int
    submission_id:   String
}

fn outcome_new(platform: String, cfg: PublishConfig, track: String) -> PublishOutcome {
    let pct: Int = 100
    if !str_eq(cfg.rollout_json, "") {
        let pct = str_to_int(json_get(cfg.rollout_json, "initial_percent"))
    }
    { "platform": platform, "version": cfg.version, "build_number": cfg.build_number,
      "track": track, "rollout_percent": pct, "submission_id": "" }
}

// ── Cert store ──────────────────────────────────────────────────────────────
//
// Tracks .p12 / .mobileprovision (Apple) and signing keystores (Google).

type CertInfo {
    kind:        String   // "apple_p12" | "apple_provisioning" | "google_keystore"
    path:        String
    expires_at:  String   // ISO 8601
    fingerprint: String
}

fn cert_is_valid(c: CertInfo) -> Bool {
    !time_after(time_now_iso(), c.expires_at)
}

fn cert_store_load(dir: String) -> String {
    // Returns JSON array of CertInfo loaded from `dir`.
    let files: String = fs_list(dir)
    let out: String = "[]"
    let n: Int = json_array_len(files)
    let i: Int = 0
    while i < n {
        let path: String = json_array_get(files, i)
        let info: CertInfo = cert_inspect(path)
        let out = json_array_push(out, json_encode(info))
        let i = i + 1
    }
    out
}

fn cert_inspect(path: String) -> CertInfo {
    let kind: String = "apple_p12"
    if str_ends_with(path, ".mobileprovision") { let kind = "apple_provisioning" }
    if str_ends_with(path, ".jks")              { let kind = "google_keystore" }
    if str_ends_with(path, ".keystore")         { let kind = "google_keystore" }
    let exp: String = exec_capture("openssl", "x509 -enddate -noout -in " + path)
    { "kind": kind, "path": path, "expires_at": exp, "fingerprint": "" }
}

// ── Metadata ────────────────────────────────────────────────────────────────

type StoreMetadata {
    title:        String
    subtitle:     String
    description:  String
    keywords:     String   // comma-separated
    privacy_url:  String
    support_url:  String
    locale:       String   // e.g. "en-US"
}

fn metadata_load(path: String, locale: String) -> StoreMetadata {
    let raw: String = fs_read(path + "/" + locale + ".toml")
    let json: String = toml_to_json(raw)
    { "title":       json_get(json, "title"),
      "subtitle":    json_get(json, "subtitle"),
      "description": json_get(json, "description"),
      "keywords":    json_get(json, "keywords"),
      "privacy_url": json_get(json, "privacy_url"),
      "support_url": json_get(json, "support_url"),
      "locale":      locale }
}

// ── Screenshots ─────────────────────────────────────────────────────────────

type ScreenshotTarget {
    platform:     String   // "apple" | "google"
    device_class: String   // "iphone-6.7" | "ipad-12.9" | "phone" | "tablet" | "tv"
    locale:       String
    expected_resolution: String   // "1290x2796"
}

fn target_iphone_6_7(locale: String) -> ScreenshotTarget {
    { "platform": "apple", "device_class": "iphone-6.7",
      "locale": locale, "expected_resolution": "1290x2796" }
}

fn target_phone_google(locale: String) -> ScreenshotTarget {
    { "platform": "google", "device_class": "phone",
      "locale": locale, "expected_resolution": "1080x1920" }
}

fn screenshot_capture(target: ScreenshotTarget, simulator_id: String, output_dir: String) -> String {
    // Drives simulator/emulator to capture at expected resolution.
    let path: String = output_dir + "/" + target.platform + "_" + target.device_class
        + "_" + target.locale + ".png"
    exec_capture("xcrun", "simctl io " + simulator_id + " screenshot " + path)
    path
}

// ── Apple publisher ─────────────────────────────────────────────────────────

fn apple_publish(cfg: PublishConfig, apple: AppleConfig, ipa_path: String, track: String) -> PublishOutcome {
    let api_key: String = secret_lookup("apple.api_key")
    let resp: String = exec_capture("xcrun", "altool --upload-app -f " + ipa_path
        + " --type ios --apiKey " + apple.api_key_id
        + " --apiIssuer " + apple.api_issuer)
    let outcome: PublishOutcome = outcome_new("apple", cfg, track)
    let submission: String = json_get(resp, "submission_id")
    { "platform": outcome.platform, "version": outcome.version,
      "build_number": outcome.build_number, "track": outcome.track,
      "rollout_percent": outcome.rollout_percent, "submission_id": submission }
}

// ── Google publisher ────────────────────────────────────────────────────────

fn google_publish(cfg: PublishConfig, google: GoogleConfig, aab_path: String) -> PublishOutcome {
    let sa_json: String = secret_lookup(google.service_account_json_ref)
    let token:   String = google_oauth_token(sa_json)
    let edit:    String = google_play_edit_create(google.package, token)
    let upload:  String = google_play_upload_aab(google.package, edit, aab_path, token)
    let assigned: String = google_play_track_assign(google.package, edit, google.track,
        cfg.version, cfg.build_number, token)
    google_play_edit_commit(google.package, edit, token)
    let outcome: PublishOutcome = outcome_new("google", cfg, google.track)
    { "platform": outcome.platform, "version": outcome.version,
      "build_number": outcome.build_number, "track": outcome.track,
      "rollout_percent": outcome.rollout_percent, "submission_id": json_get(upload, "id") }
}

// ── Rollout monitor ─────────────────────────────────────────────────────────

fn rollout_monitor(outcome: PublishOutcome, cfg: RolloutConfig) -> Bool {
    // Polls store status, advances rollout percentage in stages.
    let current: Int = outcome.rollout_percent
    while current < cfg.target_percent {
        sleep_seconds(cfg.bake_hours * 3600)
        let crash_rate: Int = fetch_crash_rate(outcome.platform, outcome.version)
        if crash_rate > 100 {   // 1% crash threshold (per 10000)
            println("[el-publish] rollout halted: crash rate too high")
            return false
        }
        let next: Int = current + 25
        if next > cfg.target_percent { let next = cfg.target_percent }
        rollout_advance(outcome, next)
        let current = next
    }
    true
}

fn rollout_advance(outcome: PublishOutcome, percent: Int) -> Bool {
    println("[el-publish] " + outcome.platform + " rollout -> " + int_to_str(percent) + "%")
    true
}

// ── Entry — smoke test ──────────────────────────────────────────────────────

let cfg: PublishConfig = publish_config_new("1.0.0", 42)
let apple: AppleConfig = { "account": "will@neurontechnologies.ai",
    "bundle_id": "ai.neurontechnologies.myapp", "team_id": "ABC123",
    "api_key_id": "KEY", "api_issuer": "ISS" }
println("[el-publish] " + cfg.version + " (" + int_to_str(cfg.build_number) + ") for " + apple.bundle_id)
