// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.interactions;

import static org.openqa.selenium.internal.Require.nonNegative;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.internal.Require;

/**
 * Models a <a href="https://www.w3.org/TR/webdriver/#dfn-wheel-input-source">wheel input
 * source</a>.
 */
@NullMarked
public class WheelInput implements InputSource, Encodable {

  private final String name;

  public WheelInput(@Nullable String name) {
    this.name = Optional.ofNullable(name).orElse(UUID.randomUUID().toString());
  }

  @Override
  public String getName() {
    return this.name;
  }

  @Override
  public SourceType getInputType() {
    return SourceType.WHEEL;
  }

  public Interaction createScroll(
      int x, int y, int deltaX, int deltaY, Duration duration, ScrollOrigin origin) {
    return new ScrollInteraction(this, x, y, deltaX, deltaY, duration, origin);
  }

  public Interaction createScroll(
      Point start, int deltaX, int deltaY, Duration duration, ScrollOrigin origin) {
    return createScroll(start.x, start.y, deltaX, deltaY, duration, origin);
  }

  @Override
  public Map<String, Object> encode() {
    Map<String, Object> toReturn = new HashMap<>();

    toReturn.put("type", getInputType().getType());
    toReturn.put("id", this.name);

    return toReturn;
  }

  static class ScrollInteraction extends Interaction implements Encodable {

    private final int x;
    private final int y;
    private final int deltaX;
    private final int deltaY;
    private final Duration duration;
    private final ScrollOrigin origin;

    protected ScrollInteraction(
        InputSource source,
        int x,
        int y,
        int deltaX,
        int deltaY,
        Duration duration,
        ScrollOrigin origin) {
      super(source);

      this.x = x;
      this.y = y;
      this.deltaX = deltaX;
      this.deltaY = deltaY;
      this.duration = nonNegative(duration);
      this.origin = Require.nonNull("Origin of scroll", origin);
    }

    @Override
    public Map<String, Object> encode() {
      Map<String, Object> toReturn = new HashMap<>();

      toReturn.put("type", "scroll");
      toReturn.put("x", x);
      toReturn.put("y", y);
      toReturn.put("deltaX", deltaX);
      toReturn.put("deltaY", deltaY);
      toReturn.put("duration", duration.toMillis());
      toReturn.put("origin", origin.asArg());

      return toReturn;
    }
  }

  public static final class ScrollOrigin {
    private final Object originObject;
    private int xOffset = 0;
    private int yOffset = 0;

    public Object asArg() {
      Object arg = originObject;
      while (arg instanceof WrapsElement) {
        arg = ((WrapsElement) arg).getWrappedElement();
      }
      return arg;
    }

    private ScrollOrigin(Object originObject, int xOffset, int yOffset) {
      this.originObject = originObject;
      this.xOffset = xOffset;
      this.yOffset = yOffset;
    }

    public static ScrollOrigin fromViewport() {
      return new ScrollOrigin("viewport", 0, 0);
    }

    public static ScrollOrigin fromViewport(int xOffset, int yOffset) {
      return new ScrollOrigin(
          "viewport", Require.nonNull("xOffset", xOffset), Require.nonNull("yOffset", yOffset));
    }

    public static ScrollOrigin fromElement(WebElement element) {
      return new ScrollOrigin(Require.nonNull("Element", element), 0, 0);
    }

    public static ScrollOrigin fromElement(WebElement element, int xOffset, int yOffset) {
      return new ScrollOrigin(
          Require.nonNull("Element", element),
          Require.nonNull("xOffset", xOffset),
          Require.nonNull("yOffset", yOffset));
    }

    public int getxOffset() {
      return xOffset;
    }

    public int getyOffset() {
      return yOffset;
    }
  }
}
