package com.anythink.reactnative.nativead;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.thinkup.nativead.api.TUNativeImageView;
import com.thinkup.nativead.api.TUNativeMaterial;
import com.thinkup.nativead.api.TUNativePrepareExInfo;
import com.thinkup.nativead.api.TUNativePrepareInfo;
import com.anythink.reactnative.utils.Const;
import com.anythink.reactnative.utils.MsgTools;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Self-rendering native reconstruction (Model A): According to the position/size/style of RN AssetView, reconstruct a set of materials with native view,
* Load the returned FrameLayout (not part of the RN view tree). Provide ATNativeHelper to SDK renderAdContainer,
* Allow ADNs such as GDT that require exclusive use of NativeAdContainer to register clicks/exposures; the same applies to ADNs such as Kuaishou.
 *
* Text/image url comes from SDK material; position/size/style measure from RN AssetView (converted to container coordinate system).
 */
public final class NativeSelfRenderRebuilder {
    private NativeSelfRenderRebuilder() {}

    /** Rebuild root tag marker: remove previous rebuild layer before re-render to avoid overlap ghosting. */
    public static final String REBUILD_ROOT_TAG = "at_selfrender_root";

    /** RN AssetView position/size/style relative to container (measure result). */
    static final class Box {
        int x, y, w, h;
        float textSizePx;
        int textColor;
        boolean hasText;
        boolean hasBg;
        int bgColor;
        int padL, padT, padR, padB;
        int gravity;
        boolean hasGravity;
    }

    /**
* Read the background color of RN AssetView (let the backgroundColor written by the developer on the AssetView be restored by native reconstruction, such as CTA blue background).
* RN backgrounds are different drawables in different versions/architectures:
* - ColorDrawable: old/simple case, getColor directly.
* - CompositeBackgroundDrawable (RN 0.85 Fabric composite background): Solid color inside background:BackgroundDrawable's backgroundColor.
* - Others with getColor(): Reflection.
* If it cannot be obtained (complex background such as rounded corners/gradients), it returns 0 (transparent), and it is up to the caller to decide.
     */
    static int readBgColor(android.graphics.drawable.Drawable bg) {
        if (bg == null) {
            return 0;
        }
        if (bg instanceof android.graphics.drawable.ColorDrawable) {
            return ((android.graphics.drawable.ColorDrawable) bg).getColor();
        }
        // CompositeBackgroundDrawable.getBackground() → BackgroundDrawable.getBackgroundColor()
        try {
            java.lang.reflect.Method getBg = bg.getClass().getMethod("getBackground");
            Object inner = getBg.invoke(bg);
            if (inner != null) {
                java.lang.reflect.Method getColor = inner.getClass().getMethod("getBackgroundColor");
                Object c = getColor.invoke(inner);
                if (c instanceof Integer) {
                    return (Integer) c;
                }
            }
        } catch (Throwable ignore) {
        }
// Don't worry: getColor() directly
        try {
            java.lang.reflect.Method m = bg.getClass().getMethod("getColor");
            Object c = m.invoke(bg);
            if (c instanceof Integer) {
                return (Integer) c;
            }
        } catch (Throwable ignore) {
        }
        return 0;
    }

    /** Measure one RN AssetView box relative to container (TextView style + background + padding). w/h<=0 is invalid. */
    static Box measure(View container, View asset) {
        Box b = new Box();
        if (asset == null) {
            return b;
        }
        b.w = asset.getWidth();
        b.h = asset.getHeight();
        int[] cLoc = new int[2];
        int[] aLoc = new int[2];
        container.getLocationInWindow(cLoc);
        asset.getLocationInWindow(aLoc);
        b.x = aLoc[0] - cLoc[0];
        b.y = aLoc[1] - cLoc[1];
//Background color: RN's backgroundColor is rendered into ColorDrawable or ReactViewBackgroundDrawable/ in the old and new architectures
// CSSBackgroundDrawable (with getColor()). The former is obtained directly, and the latter reflects getColor(), allowing developers to write it in AssetView
// The backgroundColor on can be restored (such as CTA blue background).
        b.bgColor = readBgColor(asset.getBackground());
        b.hasBg = (b.bgColor >>> 24) != 0;
        b.padL = asset.getPaddingLeft();
        b.padT = asset.getPaddingTop();
        b.padR = asset.getPaddingRight();
        b.padB = asset.getPaddingBottom();
        if (asset instanceof TextView) {
            TextView tv = (TextView) asset;
            b.textSizePx = tv.getTextSize();
            b.textColor = tv.getCurrentTextColor();
            b.hasText = true;
            b.gravity = tv.getGravity();
            b.hasGravity = true;
        }
        return b;
    }

    /**
* Reconstruct material. There is already an AssetView rendered by RN in the container (findViewById is optional).
* @param container ATNativeContainer (measure base + text style source)
* @param material SDK material (text/image url source)
* @param assetTags react tag → type name (title/desc/cta/appIcon/mainImage/mediaView/dislike)
* @param mediaView SDK getAdMediaView result (nullable; video/large picture surface)
* @param info prepareInfo to be filled (setXxxView)
* @param clickViewsOut collected clickable views (finally setClickViewList)
* @return native root FrameLayout (absolutely positioned subview), for use by renderAdContainer
     */
    public static FrameLayout build(ATNativeContainer container,
                                    TUNativeMaterial material,
                                    Map<Integer, String> assetTags,
                                    Map<Integer, Integer> assetColors,
                                    View mediaView,
                                    TUNativePrepareInfo info,
                                    List<View> clickViewsOut) {
        Context ctx = container.getContext();
        FrameLayout root = new FrameLayout(ctx);
root.setTag(REBUILD_ROOT_TAG); // Mark the reconstruction layer and remove the old one accordingly when re-rendering to prevent overlapping and ghosting
        View closeViewForFront = null;

        for (Map.Entry<Integer, String> e : assetTags.entrySet()) {
            Integer tag = e.getKey();
            String type = e.getValue();
            if (tag == null || type == null) {
                continue;
            }
            Integer jsColor = assetColors != null ? assetColors.get(tag) : null;
            View rnView = container.findViewById(tag);
            Box box = measure(container, rnView);
// The style.color passed by JS takes precedence over the color measured by measure.
            if (jsColor != null) {
                box.textColor = jsColor;
                box.hasText = true;
            }
            if (box.w <= 0 || box.h <= 0) {
                MsgTools.printMsg("rebuild skip " + type + " (no size)");
                continue;
            }
            if (Const.Native.title.equals(type)) {
                TextView tv = newText(ctx, material.getTitle(), box);
                place(root, tv, box);
                info.setTitleView(tv);
                clickViewsOut.add(tv);
            } else if (Const.Native.desc.equals(type)) {
                TextView tv = newText(ctx, material.getDescriptionText(), box);
                place(root, tv, box);
                info.setDescView(tv);
                clickViewsOut.add(tv);
            } else if (Const.Native.cta.equals(type)) {
// newText has set measure to the background (box.hasBg→RN backgroundColor) + padding + text color.
// RN AssetView has been hidden, and the CTA background is restored by measure (CompositeBackgroundDrawable can read colors).
                TextView tv = newText(ctx, material.getCallToActionText(), box);
// JS does not transmit color, and measure only measures the default translucent black/0 with white text on the bottom to ensure that the button text is clear.
                if (jsColor == null && (box.textColor == 0x8a000000 || box.textColor == 0)) {
                    tv.setTextColor(0xffffffff);
                }
                place(root, tv, box);
                info.setCtaView(tv);
                clickViewsOut.add(tv);
            } else if (Const.Native.icon.equals(type)) {
                TUNativeImageView iv = newImage(ctx, material.getIconImageUrl(), box);
                if (iv != null) {
                    place(root, iv, box);
                    info.setIconView(iv);
                    clickViewsOut.add(iv);
                }
            } else if (Const.Native.mainImage.equals(type)) {
                TUNativeImageView iv = newImage(ctx, material.getMainImageUrl(), box);
                if (iv != null) {
                    place(root, iv, box);
                    info.setMainImageView(iv);
                    clickViewsOut.add(iv);
                }
            } else if (Const.Native.dislike.equals(type)) {
                View closeView = newClose(ctx, box);
                place(root, closeView, box);
                info.setCloseView(closeView);
                closeViewForFront = closeView;
//Close button and use setCloseView + DislikeListener, do not add clickViewList
            }
        }

// media: SDK native view is placed directly into the corresponding position of the reconstruction layer (video/large image surface)
        if (mediaView != null) {
            View mv = mediaView;
            if (mv.getParent() instanceof ViewGroup) {
                ((ViewGroup) mv.getParent()).removeView(mv);
            }
            Box mbox = null;
            for (Map.Entry<Integer, String> e : assetTags.entrySet()) {
                if (Const.Native.mediaView.equals(e.getValue()) && e.getKey() != null) {
                    mbox = measure(container, container.findViewById(e.getKey()));
                    break;
                }
            }
            if (mbox != null && mbox.w > 0 && mbox.h > 0) {
                place(root, mv, mbox);
            } else {
                root.addView(mv, new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
            }
            clickViewsOut.add(mv);
        }

// The addView after media will cover the close placed first; the close button must be at the top to be clicked
        if (closeViewForFront != null) {
            closeViewForFront.bringToFront();
        }

        clickViewsOut.add(root);
        info.setClickViewList(clickViewsOut);
        MsgTools.printMsg("rebuild done: clickViews=" + clickViewsOut.size());
        return root;
    }

    private static TextView newText(Context ctx, String text, Box box) {
        TextView tv = new TextView(ctx);
        tv.setText(text != null ? text : "");
        if (box.hasText) {
            tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, box.textSizePx);
            tv.setTextColor(box.textColor);
        }
        if (box.hasGravity) {
            tv.setGravity(box.gravity);
        }
        applyBgAndPadding(tv, box);
        return tv;
    }

    /** Restore AssetView background + padding (only when developer set background on AssetView). */
    private static void applyBgAndPadding(View v, Box box) {
        if (box.hasBg) {
            v.setBackgroundColor(box.bgColor);
        }
        v.setPadding(box.padL, box.padT, box.padR, box.padB);
    }

    private static TUNativeImageView newImage(Context ctx, String url, Box box) {
        if (url == null || url.isEmpty()) {
            return null;
        }
        TUNativeImageView iv = new TUNativeImageView(ctx);
        iv.setLayoutParams(new FrameLayout.LayoutParams(box.w, box.h));
// CENTER_CROP is filled without deformation (otherwise the square image is stretched/leaved to reveal the background color)
        iv.setScaleType(android.widget.ImageView.ScaleType.CENTER_CROP);
        iv.setImage(url);
        return iv;
    }

    /** Close/dislike button: rebuild from RN ATNativeCloseView position/size, circular bg + system close icon. */
    private static View newClose(Context ctx, Box box) {
        android.widget.ImageView iv = new android.widget.ImageView(ctx);
        iv.setImageResource(resolveSystemCloseDrawable());
        iv.setScaleType(android.widget.ImageView.ScaleType.CENTER_INSIDE);
        int bgColor = box.hasBg ? box.bgColor : 0x66000000;
        applyCircularBackground(iv, bgColor);
        int pad = Math.max(2, Math.min(box.w, box.h) / 6);
        iv.setPadding(
                box.padL > 0 ? box.padL : pad,
                box.padT > 0 ? box.padT : pad,
                box.padR > 0 ? box.padR : pad,
                box.padB > 0 ? box.padB : pad);
        return iv;
    }

    /** Circular semi-transparent bg (align JS borderRadius: size/2; avoid square corners from setBackgroundColor). */
    private static void applyCircularBackground(View v, int color) {
        android.graphics.drawable.GradientDrawable bg =
                new android.graphics.drawable.GradientDrawable();
        bg.setShape(android.graphics.drawable.GradientDrawable.OVAL);
        bg.setColor(color);
        v.setBackground(bg);
    }

    /** System close icon: prefer ic_clear, fallback ic_menu_close_clear_cancel. */
    private static int resolveSystemCloseDrawable() {
        try {
            return android.R.drawable.class.getField("ic_clear").getInt(null);
        } catch (Throwable ignore) {
            return android.R.drawable.ic_menu_close_clear_cancel;
        }
    }

    /** Absolutely position rebuilt asset views into rebuild root using measured position/size. */
    private static void place(FrameLayout root, View v, Box box) {
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(box.w, box.h);
        lp.leftMargin = box.x;
        lp.topMargin = box.y;
        root.addView(v, lp);
    }
}
