/*
 * ElBridge.java — Android Java companion to el_android.c.
 *
 * All public methods are static. The C JNI layer calls these to create views,
 * set properties, and manage the widget tree. Views are identified by integer
 * slot indices matching the C-side handle values.
 *
 * Threading: every method that touches a View dispatches to the UI thread
 * using Activity.runOnUiThread(Runnable) and blocks with a CountDownLatch
 * until the UI thread completes the operation. This mirrors the AppKit
 * dispatch_sync(main_queue, ^{}) pattern in el_appkit.m.
 *
 * Callbacks: Java sets listeners on views that call back into C via:
 *   nativeOnClick(int slot)
 *   nativeOnChange(int slot, String text)
 *   nativeOnSubmit(int slot, String text)
 * These are declared native and implemented in el_android.c.
 *
 * Usage (in your Activity.onCreate):
 *   System.loadLibrary("elruntime");
 *   ElBridge.init(this);
 *
 * The native library calls __native_init() which calls nativeRegisterActivity
 * via the C side; alternatively call ElBridge.init(this) directly from Java.
 *
 * Compile requirements:
 *   Android minSdkVersion 21 (Lollipop) or higher.
 *   No third-party dependencies — uses only android.* framework classes.
 *   For image loading from arbitrary file paths, BitmapFactory is used.
 *   To replace with Glide/Picasso, edit createImageView only.
 */

package com.neuron.el;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import java.util.concurrent.CountDownLatch;

public class ElBridge {

    /* ── Native callbacks (implemented in el_android.c) ─────────────────── */

    public static native void nativeOnClick(int slot);
    public static native void nativeOnChange(int slot, String text);
    public static native void nativeOnSubmit(int slot, String text);
    public static native void nativeRegisterActivity(Activity activity);

    /* ── State ───────────────────────────────────────────────────────────── */

    private static final int MAX_SLOTS = 4096;

    private static Activity   sActivity;
    private static Handler    sUiHandler;
    private static View[]     sViews    = new View[MAX_SLOTS];
    private static int        sNextSlot = 1;   /* slot 0 reserved / null */

    /* ── Init ────────────────────────────────────────────────────────────── */

    /**
     * Must be called from the Activity before any widget operations.
     * Typically called from Activity.onCreate after System.loadLibrary.
     */
    public static void init(Activity activity) {
        sActivity  = activity;
        sUiHandler = new Handler(Looper.getMainLooper());
        nativeRegisterActivity(activity);
    }

    /* ── Slot management ─────────────────────────────────────────────────── */

    private static int allocSlot(View v) {
        /* Find a free slot starting from sNextSlot, wrap around. */
        for (int i = 0; i < MAX_SLOTS - 1; i++) {
            int idx = ((sNextSlot - 1 + i) % (MAX_SLOTS - 1)) + 1;
            if (sViews[idx] == null) {
                sViews[idx] = v;
                sNextSlot = (idx % (MAX_SLOTS - 1)) + 1;
                return idx;
            }
        }
        android.util.Log.e("ElBridge", "allocSlot: slot table full");
        return -1;
    }

    private static View getView(int slot) {
        if (slot <= 0 || slot >= MAX_SLOTS) return null;
        return sViews[slot];
    }

    /* ── UI-thread dispatch helper ───────────────────────────────────────── */

    /*
     * Dispatch r on the UI thread and block until it completes.
     * Safe to call from the UI thread itself (runs inline without posting).
     */
    private static void runSync(final Runnable r) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            r.run();
        } else {
            final CountDownLatch latch = new CountDownLatch(1);
            sUiHandler.post(new Runnable() {
                @Override public void run() {
                    try { r.run(); } finally { latch.countDown(); }
                }
            });
            try { latch.await(); } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /* ── Integer slot returning runSync helper ───────────────────────────── */

    private interface IntSupplier { int get(); }

    private static int runSyncInt(final IntSupplier s) {
        final int[] result = { -1 };
        runSync(new Runnable() {
            @Override public void run() { result[0] = s.get(); }
        });
        return result[0];
    }

    /* ── Context accessor ────────────────────────────────────────────────── */

    private static Context ctx() { return sActivity; }

    /* ── View creation ───────────────────────────────────────────────────── */

    /**
     * Create a LinearLayout.
     * @param orientation 1=VERTICAL, 0=HORIZONTAL
     * @param spacing     gap between children in dp; applied as bottom/right margin
     */
    public static int createLinearLayout(final int orientation, final int spacing) {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                LinearLayout ll = new LinearLayout(ctx());
                ll.setOrientation(orientation == 1
                        ? LinearLayout.VERTICAL
                        : LinearLayout.HORIZONTAL);
                ll.setLayoutParams(new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                /* Spacing is stored so addChild can apply margins. */
                ll.setTag(R_TAG_SPACING, spacing);
                return allocSlot(ll);
            }
        });
    }

    /** Create a FrameLayout (ZStack equivalent). */
    public static int createFrameLayout() {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                FrameLayout fl = new FrameLayout(ctx());
                fl.setLayoutParams(new FrameLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                return allocSlot(fl);
            }
        });
    }

    /** Create a ScrollView. */
    public static int createScrollView() {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                ScrollView sv = new ScrollView(ctx());
                sv.setLayoutParams(new ScrollView.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
                sv.setFillViewport(true);
                return allocSlot(sv);
            }
        });
    }

    /** Create a TextView with initial text. */
    public static int createTextView(final String text) {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                TextView tv = new TextView(ctx());
                tv.setText(text != null ? text : "");
                tv.setLayoutParams(new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                return allocSlot(tv);
            }
        });
    }

    /** Create a Button with a label. */
    public static int createButton(final String label) {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                Button btn = new Button(ctx());
                btn.setText(label != null ? label : "");
                btn.setLayoutParams(new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                return allocSlot(btn);
            }
        });
    }

    /**
     * Create an EditText.
     * @param placeholder hint text
     * @param singleLine  true = single-line text field; false = multi-line text area
     */
    public static int createEditText(final String placeholder, final boolean singleLine) {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                EditText et = new EditText(ctx());
                et.setHint(placeholder != null ? placeholder : "");
                if (singleLine) {
                    et.setInputType(InputType.TYPE_CLASS_TEXT
                            | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
                    et.setMaxLines(1);
                    et.setSingleLine(true);
                } else {
                    et.setInputType(InputType.TYPE_CLASS_TEXT
                            | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
                    et.setMinLines(3);
                    et.setSingleLine(false);
                    et.setGravity(Gravity.TOP | Gravity.START);
                }
                et.setLayoutParams(new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                return allocSlot(et);
            }
        });
    }

    /**
     * Create an ImageView, loading from a file path via BitmapFactory.
     * If path is null/empty the ImageView is created with no image.
     */
    public static int createImageView(final String path) {
        return runSyncInt(new IntSupplier() {
            @Override public int get() {
                ImageView iv = new ImageView(ctx());
                iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
                iv.setAdjustViewBounds(true);
                if (path != null && !path.isEmpty()) {
                    Bitmap bmp = BitmapFactory.decodeFile(path);
                    if (bmp != null) {
                        iv.setImageBitmap(bmp);
                    } else {
                        android.util.Log.w("ElBridge",
                                "createImageView: failed to decode " + path);
                    }
                }
                iv.setLayoutParams(new LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                return allocSlot(iv);
            }
        });
    }

    /* ── Window operations ───────────────────────────────────────────────── */

    /** Set the Activity's content view to the view at slot. */
    public static void setContentView(final int slot) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v != null && sActivity != null) {
                    sActivity.setContentView(v);
                }
            }
        });
    }

    /** Set the Activity title. */
    public static void setTitle(final String title) {
        runSync(new Runnable() {
            @Override public void run() {
                if (sActivity != null) {
                    sActivity.setTitle(title != null ? title : "");
                }
            }
        });
    }

    /* ── Tree operations ─────────────────────────────────────────────────── */

    /**
     * Add child view to parent view.
     * LinearLayout: child added as arranged child with spacing margin.
     * ScrollView: child replaces current document view.
     * FrameLayout / other ViewGroup: plain addView.
     */
    public static void addChild(final int parentSlot, final int childSlot) {
        runSync(new Runnable() {
            @Override public void run() {
                View parent = getView(parentSlot);
                View child  = getView(childSlot);
                if (parent == null || child == null) return;

                /* Remove child from existing parent first. */
                if (child.getParent() instanceof ViewGroup) {
                    ((ViewGroup) child.getParent()).removeView(child);
                }

                if (parent instanceof LinearLayout) {
                    LinearLayout ll = (LinearLayout) parent;
                    Object tag = ll.getTag(R_TAG_SPACING);
                    int spacing = (tag instanceof Integer) ? (Integer) tag : 0;
                    LinearLayout.LayoutParams lp;
                    Object existingLp = child.getLayoutParams();
                    if (existingLp instanceof LinearLayout.LayoutParams) {
                        lp = (LinearLayout.LayoutParams) existingLp;
                    } else {
                        lp = new LinearLayout.LayoutParams(
                                ViewGroup.LayoutParams.WRAP_CONTENT,
                                ViewGroup.LayoutParams.WRAP_CONTENT);
                    }
                    /* Apply spacing as margin on the leading/top edge (after first child). */
                    if (ll.getChildCount() > 0 && spacing > 0) {
                        int px = dpToPx(spacing);
                        if (ll.getOrientation() == LinearLayout.VERTICAL) {
                            lp.topMargin = px;
                        } else {
                            lp.leftMargin = px;
                        }
                    }
                    child.setLayoutParams(lp);
                    ll.addView(child);
                } else if (parent instanceof ScrollView) {
                    ScrollView sv = (ScrollView) parent;
                    sv.removeAllViews();
                    sv.addView(child);
                } else if (parent instanceof ViewGroup) {
                    ((ViewGroup) parent).addView(child);
                }
            }
        });
    }

    /** Remove child from its parent. */
    public static void removeChild(final int parentSlot, final int childSlot) {
        runSync(new Runnable() {
            @Override public void run() {
                View parent = getView(parentSlot);
                View child  = getView(childSlot);
                if (parent instanceof ViewGroup && child != null) {
                    ((ViewGroup) parent).removeView(child);
                }
            }
        });
    }

    /** Remove the view from its parent and release the slot. */
    public static void destroyView(final int slot) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                if (v.getParent() instanceof ViewGroup) {
                    ((ViewGroup) v.getParent()).removeView(v);
                }
                sViews[slot] = null;
            }
        });
    }

    /* ── Property setters ────────────────────────────────────────────────── */

    public static void setText(final int slot, final String text) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                String s = text != null ? text : "";
                if (v instanceof EditText) {
                    ((EditText) v).setText(s);
                } else if (v instanceof Button) {
                    ((Button) v).setText(s);
                } else if (v instanceof TextView) {
                    ((TextView) v).setText(s);
                }
            }
        });
    }

    public static String getText(final int slot) {
        final String[] result = { "" };
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v instanceof TextView) {
                    CharSequence cs = ((TextView) v).getText();
                    result[0] = cs != null ? cs.toString() : "";
                }
            }
        });
        return result[0];
    }

    /** Set foreground text color. Components in [0,1]. */
    public static void setTextColor(final int slot, final float r, final float g,
                                     final float b, final float a) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v instanceof TextView) {
                    ((TextView) v).setTextColor(floatToArgb(r, g, b, a));
                }
            }
        });
    }

    /** Set background color using a GradientDrawable so corner radius is preserved. */
    public static void setBackgroundColor(final int slot, final float r, final float g,
                                           final float b, final float a) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                ensureGradientBackground(v);
                GradientDrawable gd = (GradientDrawable) v.getBackground();
                gd.setColor(floatToArgb(r, g, b, a));
            }
        });
    }

    /**
     * Set font family and size.
     * family: "system" or null → system default; otherwise tries to load by name.
     * bold: if true uses Typeface.BOLD.
     */
    public static void setFont(final int slot, final String family,
                                final int sizeSp, final boolean bold) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (!(v instanceof TextView)) return;
                TextView tv = (TextView) v;
                Typeface tf;
                if (family != null && !family.isEmpty()
                        && !family.equals("system")) {
                    Typeface base = Typeface.create(family,
                            bold ? Typeface.BOLD : Typeface.NORMAL);
                    tf = (base != null) ? base
                            : Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
                } else {
                    tf = Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
                }
                tv.setTypeface(tf);
                tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sizeSp);
            }
        });
    }

    /** Set padding in dp. */
    public static void setPadding(final int slot, final int top, final int right,
                                   final int bottom, final int left) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v != null) {
                    v.setPadding(dpToPx(left), dpToPx(top), dpToPx(right), dpToPx(bottom));
                }
            }
        });
    }

    /** Set explicit width in dp. Passes MATCH_PARENT for negative values. */
    public static void setWidth(final int slot, final int widthDp) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                ViewGroup.LayoutParams lp = v.getLayoutParams();
                if (lp == null) lp = new ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                lp.width = widthDp < 0
                        ? ViewGroup.LayoutParams.MATCH_PARENT
                        : dpToPx(widthDp);
                v.setLayoutParams(lp);
            }
        });
    }

    /** Set explicit height in dp. */
    public static void setHeight(final int slot, final int heightDp) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                ViewGroup.LayoutParams lp = v.getLayoutParams();
                if (lp == null) lp = new ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                lp.height = heightDp < 0
                        ? ViewGroup.LayoutParams.MATCH_PARENT
                        : dpToPx(heightDp);
                v.setLayoutParams(lp);
            }
        });
    }

    /**
     * Set flex weight on a child of a LinearLayout.
     * flex > 0 → weight = flex, width/height = 0dp (expand).
     * flex == 0 → weight = 0, wrap_content (shrink to content).
     */
    public static void setFlex(final int slot, final int flex) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                ViewGroup.LayoutParams lp = v.getLayoutParams();
                if (lp instanceof LinearLayout.LayoutParams) {
                    LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams) lp;
                    if (flex > 0) {
                        llp.weight = (float) flex;
                        /* Determine orientation from parent to set 0dp on the right axis. */
                        if (v.getParent() instanceof LinearLayout) {
                            LinearLayout parent = (LinearLayout) v.getParent();
                            if (parent.getOrientation() == LinearLayout.VERTICAL) {
                                llp.height = 0;
                            } else {
                                llp.width = 0;
                            }
                        }
                    } else {
                        llp.weight = 0f;
                    }
                    v.setLayoutParams(llp);
                }
            }
        });
    }

    /** Set corner radius in dp using a GradientDrawable background. */
    public static void setCornerRadius(final int slot, final float radiusDp) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                ensureGradientBackground(v);
                GradientDrawable gd = (GradientDrawable) v.getBackground();
                gd.setCornerRadius(dpToPxF(radiusDp));
            }
        });
    }

    public static void setEnabled(final int slot, final boolean enabled) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v != null) v.setEnabled(enabled);
            }
        });
    }

    /**
     * Show or hide a view.
     * @param visible true = VISIBLE, false = GONE (matches AppKit setHidden semantics)
     */
    public static void setVisibility(final int slot, final boolean visible) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v != null) v.setVisibility(visible ? View.VISIBLE : View.GONE);
            }
        });
    }

    /* ── Event listener registration ─────────────────────────────────────── */

    /** Register an OnClickListener that calls back into C nativeOnClick. */
    public static void setOnClickListener(final int slot) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (v == null) return;
                final int capturedSlot = slot;
                v.setOnClickListener(new View.OnClickListener() {
                    @Override public void onClick(View view) {
                        nativeOnClick(capturedSlot);
                    }
                });
            }
        });
    }

    /**
     * Register a TextWatcher on an EditText that calls back nativeOnChange
     * for every text change.
     */
    public static void setOnChangeListener(final int slot) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (!(v instanceof EditText)) return;
                final int capturedSlot = slot;
                ((EditText) v).addTextChangedListener(new TextWatcher() {
                    @Override public void beforeTextChanged(CharSequence s, int start,
                                                             int count, int after) {}
                    @Override public void onTextChanged(CharSequence s, int start,
                                                         int before, int count) {}
                    @Override public void afterTextChanged(Editable s) {
                        nativeOnChange(capturedSlot, s != null ? s.toString() : "");
                    }
                });
            }
        });
    }

    /**
     * Register an OnEditorActionListener on a single-line EditText that calls
     * nativeOnSubmit when the user presses the action/enter key.
     */
    public static void setOnSubmitListener(final int slot) {
        runSync(new Runnable() {
            @Override public void run() {
                View v = getView(slot);
                if (!(v instanceof EditText)) return;
                final int capturedSlot = slot;
                ((EditText) v).setOnEditorActionListener(
                        new TextView.OnEditorActionListener() {
                    @Override
                    public boolean onEditorAction(TextView tv, int actionId,
                                                   android.view.KeyEvent event) {
                        nativeOnSubmit(capturedSlot,
                                tv.getText() != null ? tv.getText().toString() : "");
                        return true;
                    }
                });
            }
        });
    }

    /* ── Internal helpers ─────────────────────────────────────────────────── */

    /*
     * Tag key used to stash the spacing value on LinearLayouts so addChild
     * can apply the correct margin between children.
     * We use a stable integer resource-id-like value; because we do not have
     * a resources file here we use View.generateViewId() lazily.
     */
    private static int sSpacingTagKey = 0;

    private static int R_TAG_SPACING;
    static {
        R_TAG_SPACING = View.generateViewId();
    }

    /** Convert dp to pixels using the Activity's display metrics. */
    private static int dpToPx(float dp) {
        if (sActivity == null) return (int) dp;
        float density = sActivity.getResources().getDisplayMetrics().density;
        return Math.round(dp * density);
    }

    private static float dpToPxF(float dp) {
        if (sActivity == null) return dp;
        float density = sActivity.getResources().getDisplayMetrics().density;
        return dp * density;
    }

    /** Convert RGBA float components (0–1) to an Android ARGB int. */
    private static int floatToArgb(float r, float g, float b, float a) {
        int ai = Math.round(a * 255f);
        int ri = Math.round(r * 255f);
        int gi = Math.round(g * 255f);
        int bi = Math.round(b * 255f);
        return Color.argb(ai, ri, gi, bi);
    }

    /**
     * Ensure the view has a GradientDrawable background so that both color
     * and corner radius can be set independently.  If the current background
     * is already a GradientDrawable it is reused; otherwise a new transparent
     * one is installed.
     */
    private static void ensureGradientBackground(View v) {
        if (!(v.getBackground() instanceof GradientDrawable)) {
            GradientDrawable gd = new GradientDrawable();
            gd.setColor(Color.TRANSPARENT);
            v.setBackground(gd);
        }
    }
}
