/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.views.drawer;

import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.drawerlayout.widget.DrawerLayout;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.viewmanagers.AndroidDrawerLayoutManagerDelegate;
import com.facebook.react.viewmanagers.AndroidDrawerLayoutManagerInterface;
import com.facebook.react.views.drawer.events.DrawerClosedEvent;
import com.facebook.react.views.drawer.events.DrawerOpenedEvent;
import com.facebook.react.views.drawer.events.DrawerSlideEvent;
import com.facebook.react.views.drawer.events.DrawerStateChangedEvent;
import java.util.HashMap;
import java.util.Map;

/** View Manager for {@link ReactDrawerLayout} components. */
@ReactModule(name = ReactDrawerLayoutManager.REACT_CLASS)
public class ReactDrawerLayoutManager extends ViewGroupManager<ReactDrawerLayout>
    implements AndroidDrawerLayoutManagerInterface<ReactDrawerLayout> {

  public static final String REACT_CLASS = "AndroidDrawerLayout";

  public static final int OPEN_DRAWER = 1;
  public static final int CLOSE_DRAWER = 2;

  private final ViewManagerDelegate<ReactDrawerLayout> mDelegate;

  public ReactDrawerLayoutManager() {
    mDelegate = new AndroidDrawerLayoutManagerDelegate<>(this);
  }

  @Override
  public @NonNull String getName() {
    return REACT_CLASS;
  }

  @Override
  protected void addEventEmitters(ThemedReactContext reactContext, ReactDrawerLayout view) {
    EventDispatcher eventDispatcher =
        UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.getId());
    if (eventDispatcher == null) {
      return;
    }

    view.addDrawerListener(new DrawerEventEmitter(view, eventDispatcher));
  }

  @Override
  protected @NonNull ReactDrawerLayout createViewInstance(@NonNull ThemedReactContext context) {
    return new ReactDrawerLayout(context);
  }

  @Override
  public void setDrawerPosition(ReactDrawerLayout view, @Nullable String value) {
    if (value == null) {
      view.setDrawerPosition(Gravity.START);
    } else {
      setDrawerPositionInternal(view, value);
    }
  }

  @ReactProp(name = "drawerPosition")
  public void setDrawerPosition(ReactDrawerLayout view, Dynamic drawerPosition) {
    if (drawerPosition.isNull()) {
      view.setDrawerPosition(Gravity.START);
    } else if (drawerPosition.getType() == ReadableType.Number) {
      final int drawerPositionNum = drawerPosition.asInt();

      if (Gravity.START == drawerPositionNum || Gravity.END == drawerPositionNum) {
        view.setDrawerPosition(drawerPositionNum);
      } else {
        throw new JSApplicationIllegalArgumentException(
            "Unknown drawerPosition " + drawerPositionNum);
      }
    } else if (drawerPosition.getType() == ReadableType.String) {
      setDrawerPositionInternal(view, drawerPosition.asString());
    } else {
      throw new JSApplicationIllegalArgumentException("drawerPosition must be a string or int");
    }
  }

  private void setDrawerPositionInternal(ReactDrawerLayout view, String drawerPosition) {
    if (drawerPosition.equals("left")) {
      view.setDrawerPosition(Gravity.START);
    } else if (drawerPosition.equals("right")) {
      view.setDrawerPosition(Gravity.END);
    } else {
      throw new JSApplicationIllegalArgumentException(
          "drawerPosition must be 'left' or 'right', received" + drawerPosition);
    }
  }

  @ReactProp(name = "drawerWidth", defaultFloat = Float.NaN)
  public void setDrawerWidth(ReactDrawerLayout view, float width) {
    int widthInPx =
        Float.isNaN(width)
            ? ReactDrawerLayout.DEFAULT_DRAWER_WIDTH
            : Math.round(PixelUtil.toPixelFromDIP(width));
    view.setDrawerWidth(widthInPx);
  }

  @Override
  public void setDrawerWidth(ReactDrawerLayout view, @Nullable Float width) {
    int widthInPx =
        width == null
            ? ReactDrawerLayout.DEFAULT_DRAWER_WIDTH
            : Math.round(PixelUtil.toPixelFromDIP(width));
    view.setDrawerWidth(widthInPx);
  }

  @Override
  @ReactProp(name = "drawerLockMode")
  public void setDrawerLockMode(ReactDrawerLayout view, @Nullable String drawerLockMode) {
    if (drawerLockMode == null || "unlocked".equals(drawerLockMode)) {
      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
    } else if ("locked-closed".equals(drawerLockMode)) {
      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
    } else if ("locked-open".equals(drawerLockMode)) {
      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
    } else {
      throw new JSApplicationIllegalArgumentException("Unknown drawerLockMode " + drawerLockMode);
    }
  }

  @Override
  public void openDrawer(ReactDrawerLayout view) {
    view.openDrawer();
  }

  @Override
  public void closeDrawer(ReactDrawerLayout view) {
    view.closeDrawer();
  }

  @Override
  @ReactProp(name = "keyboardDismissMode")
  public void setKeyboardDismissMode(ReactDrawerLayout view, @Nullable String value) {}

  @Override
  @ReactProp(name = "drawerBackgroundColor", customType = "Color")
  public void setDrawerBackgroundColor(ReactDrawerLayout view, @Nullable Integer value) {}

  @Override
  @ReactProp(name = "statusBarBackgroundColor", customType = "Color")
  public void setStatusBarBackgroundColor(ReactDrawerLayout view, @Nullable Integer value) {}

  @Override
  public void setElevation(@NonNull ReactDrawerLayout view, float elevation) {
    view.setDrawerElevation(PixelUtil.toPixelFromDIP(elevation));
  }

  @Override
  public boolean needsCustomLayoutForChildren() {
    // Return true, since DrawerLayout will lay out it's own children.
    return true;
  }

  @Override
  public @Nullable Map<String, Integer> getCommandsMap() {
    return MapBuilder.of("openDrawer", OPEN_DRAWER, "closeDrawer", CLOSE_DRAWER);
  }

  @Override
  public void receiveCommand(ReactDrawerLayout root, int commandId, @Nullable ReadableArray args) {
    switch (commandId) {
      case OPEN_DRAWER:
        root.openDrawer();
        break;
      case CLOSE_DRAWER:
        root.closeDrawer();
        break;
    }
  }

  @Override
  public void receiveCommand(
      @NonNull ReactDrawerLayout root, String commandId, @Nullable ReadableArray args) {
    switch (commandId) {
      case "openDrawer":
        root.openDrawer();
        break;
      case "closeDrawer":
        root.closeDrawer();
        break;
    }
  }

  @Override
  public @Nullable Map getExportedViewConstants() {
    return MapBuilder.of(
        "DrawerPosition", MapBuilder.of("Left", Gravity.START, "Right", Gravity.END));
  }

  @Override
  public @Nullable Map getExportedCustomDirectEventTypeConstants() {
    @Nullable
    Map<String, Object> baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants();
    Map<String, Object> eventTypeConstants =
        baseEventTypeConstants == null ? new HashMap<String, Object>() : baseEventTypeConstants;
    eventTypeConstants.putAll(
        MapBuilder.of(
            DrawerSlideEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerSlide"),
            DrawerOpenedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerOpen"),
            DrawerClosedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDrawerClose"),
            DrawerStateChangedEvent.EVENT_NAME,
                MapBuilder.of("registrationName", "onDrawerStateChanged")));
    return eventTypeConstants;
  }

  /**
   * This method is overridden because of two reasons: 1. A drawer must have exactly two children 2.
   * The second child that is added, is the navigationView, which gets panned from the side.
   */
  @Override
  public void addView(ReactDrawerLayout parent, View child, int index) {
    if (getChildCount(parent) >= 2) {
      throw new JSApplicationIllegalArgumentException(
          "The Drawer cannot have more than two children");
    }
    if (index != 0 && index != 1) {
      throw new JSApplicationIllegalArgumentException(
          "The only valid indices for drawer's child are 0 or 1. Got " + index + " instead.");
    }
    parent.addView(child, index);
    parent.setDrawerProperties();
  }

  @Override
  public ViewManagerDelegate<ReactDrawerLayout> getDelegate() {
    return mDelegate;
  }

  public static class DrawerEventEmitter implements DrawerLayout.DrawerListener {

    private final DrawerLayout mDrawerLayout;
    private final EventDispatcher mEventDispatcher;

    public DrawerEventEmitter(DrawerLayout drawerLayout, EventDispatcher eventDispatcher) {
      mDrawerLayout = drawerLayout;
      mEventDispatcher = eventDispatcher;
    }

    @Override
    public void onDrawerSlide(@NonNull View view, float v) {
      mEventDispatcher.dispatchEvent(
          new DrawerSlideEvent(
              UIManagerHelper.getSurfaceId(mDrawerLayout), mDrawerLayout.getId(), v));
    }

    @Override
    public void onDrawerOpened(@NonNull View view) {
      mEventDispatcher.dispatchEvent(
          new DrawerOpenedEvent(
              UIManagerHelper.getSurfaceId(mDrawerLayout), mDrawerLayout.getId()));
    }

    @Override
    public void onDrawerClosed(@NonNull View view) {
      mEventDispatcher.dispatchEvent(
          new DrawerClosedEvent(
              UIManagerHelper.getSurfaceId(mDrawerLayout), mDrawerLayout.getId()));
    }

    @Override
    public void onDrawerStateChanged(int i) {
      mEventDispatcher.dispatchEvent(
          new DrawerStateChangedEvent(
              UIManagerHelper.getSurfaceId(mDrawerLayout), mDrawerLayout.getId(), i));
    }
  }
}
