package chat.vojo.app;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;

import java.util.Locale;

/**
 * Locale-aware push-notification strings. Resources (values{,-ru}/
 * push_strings.xml) are auto-generated at build time by
 * scripts/gen-push-strings.mjs from public/locales/{en,ru}.json so
 * the Push namespace has a single source of truth shared with the
 * web Service Worker. Do not edit push_strings.xml by hand — it will
 * be overwritten on the next `npm run android:sync`.
 *
 * Locale selection: Vojo ships an explicit in-app language picker that
 * does NOT have to match the device locale. pushLanguageBridge.ts
 * mirrors i18next's current language into Capacitor Preferences
 * (shared_prefs/CapacitorStorage.xml, key "vojo.appLanguage") on every
 * `languageChanged` event; we read it here and force it onto a
 * Configuration-scoped Context before the getString call.
 *
 * Killed-process pushes may arrive before JS has ever booted — no pref
 * to read. In that case we fall back to the device locale, normalised
 * to {en, ru}; anything else maps to en, matching i18n.ts
 * fallbackLng: 'en' on the main thread.
 */
final class PushStrings {

    private static final String PREFS_GROUP = "CapacitorStorage";
    private static final String LANG_KEY = "vojo.appLanguage";

    private PushStrings() {}

    static String messageFallback(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_new_message);
    }

    static String messagesFallback(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_new_messages);
    }

    static String inviteTitle(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_invitation);
    }

    static String missedCallTitle(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_missed_call);
    }

    static String missedCallBody(Context ctx, String caller) {
        String safeCaller = caller == null ? "" : caller;
        return forAppLocale(ctx).getString(R.string.push_missed_call_body, safeCaller);
    }

    static String channelGroup(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_channel_group);
    }

    static String channelDm(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_channel_dm);
    }

    static String channelDmDescription(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_channel_dm_description);
    }

    static String channelGroupRoom(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_channel_group_room);
    }

    static String channelGroupRoomDescription(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_channel_group_room_description);
    }

    static String selfName(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_self_name);
    }

    static String markAsReadAction(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_action_mark_as_read);
    }

    static String replyAction(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_action_reply);
    }

    static String replyHint(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_reply_hint);
    }

    static String replyFailed(Context ctx) {
        return forAppLocale(ctx).getString(R.string.push_reply_failed);
    }

    /**
     * Build the invite-notification body from inviter + room name, falling
     * back through four variants when one or both are absent. The res IDs
     * all declare positional formatters (%1$s = inviter, %2$s = roomName)
     * to keep the order stable across locales; we always pass both args
     * even when only one is used, because Android's formatter requires
     * every referenced position to be supplied.
     */
    static String inviteBody(Context ctx, String inviter, String roomName) {
        boolean hasInviter = inviter != null && !inviter.isEmpty();
        boolean hasRoom = roomName != null && !roomName.isEmpty();
        int resId;
        if (hasInviter && hasRoom) {
            resId = R.string.push_invite_body;
        } else if (hasInviter) {
            resId = R.string.push_invite_body_no_room;
        } else if (hasRoom) {
            resId = R.string.push_invite_body_no_inviter;
        } else {
            resId = R.string.push_invite_body_generic;
        }
        String safeInviter = hasInviter ? inviter : "";
        String safeRoom = hasRoom ? roomName : "";
        return forAppLocale(ctx).getString(resId, safeInviter, safeRoom);
    }

    private static Context forAppLocale(Context ctx) {
        String lang = chooseLang(ctx);
        try {
            Configuration cfg = new Configuration(ctx.getResources().getConfiguration());
            cfg.setLocale(Locale.forLanguageTag(lang));
            return ctx.createConfigurationContext(cfg);
        } catch (Throwable t) {
            return ctx;
        }
    }

    private static String chooseLang(Context ctx) {
        String fromPref = readAppLanguage(ctx);
        if (fromPref != null) return fromPref;
        try {
            Locale loc = Locale.getDefault();
            if (loc != null) return normalize(loc.getLanguage());
        } catch (Throwable ignored) {
            // Locale.getDefault is unusually robust but defensively fall through.
        }
        return "en";
    }

    private static String readAppLanguage(Context ctx) {
        try {
            SharedPreferences prefs =
                ctx.getSharedPreferences(PREFS_GROUP, Context.MODE_PRIVATE);
            return normalize(prefs.getString(LANG_KEY, null));
        } catch (Throwable t) {
            return null;
        }
    }

    // Match i18next config: `fallbackLng: 'en'`, `load: 'languageOnly'`.
    // Vojo only ships en.json + ru.json; any other Configuration locale
    // (e.g. "fr") would bypass both values-ru/ and our bundled resources
    // and land on values/ anyway — we collapse to "en" explicitly so the
    // web and native surfaces render the same lingua-franca default and
    // readers of this method don't have to reason about fall-through.
    // Clamp on both write (pushLanguageBridge.ts) and read (here) so a
    // stale or tampered pref value can't leak through.
    private static String normalize(String raw) {
        if (raw == null) return null;
        String lower = raw.toLowerCase(Locale.ROOT);
        if (lower.startsWith("ru")) return "ru";
        return "en";
    }
}
