import { Platform } from 'react-native';

import { type ActionType } from '../../ActionType';
import { State } from '../../State';
import { deepEqual } from '../../utils';
import type { NativeHandlerData } from '../../v3/hooks/gestures/native/NativeTypes';
import type { HandlerData } from '../../v3/types';
import { SingleGestureName } from '../../v3/types';
import {
  DEFAULT_TOUCH_SLOP,
  NATIVE_GESTURE_ROLE_ATTRIBUTE,
} from '../constants';
import type { AdaptedEvent, Config, PropsRef } from '../interfaces';
import { NativeGestureRole } from '../interfaces';
import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate';
import {
  dispatchGestureLifecycleEvent,
  GestureLifecycleEvent,
} from '../tools/GestureLifecycleEvents';
import GestureHandler from './GestureHandler';
import type IGestureHandler from './IGestureHandler';

export default class NativeViewGestureHandler extends GestureHandler {
  public override readonly isContinuous = true;
  private role: NativeGestureRole | null = null;

  // TODO: Implement logic for activation on start properly
  private shouldActivateOnStart = false;
  private disallowInterruption = false;
  private yieldsToContinuousGestures = false;

  private startX = 0;
  private startY = 0;
  private minDistSq = DEFAULT_TOUCH_SLOP * DEFAULT_TOUCH_SLOP;

  private lastActiveHandlerData: HandlerData<NativeHandlerData> | null = null;

  public constructor(
    delegate: GestureHandlerDelegate<unknown, IGestureHandler>
  ) {
    super(delegate);
    this.name = SingleGestureName.Native;
  }

  public override init(
    ref: number,
    propsRef: React.RefObject<PropsRef>,
    actionType: ActionType
  ): void {
    super.init(ref, propsRef, actionType);

    this.shouldCancelWhenOutside = true;

    if (Platform.OS !== 'web') {
      return;
    }

    const view = this.delegate.view as HTMLElement;

    this.restoreViewStyles(view);

    if (this.usesNativeOrVirtualDetector()) {
      this.role =
        (view.getAttribute(
          NATIVE_GESTURE_ROLE_ATTRIBUTE
        ) as NativeGestureRole) ?? null;
    } else {
      if (view.getAttribute('role') === 'button') {
        this.role = NativeGestureRole.Button;
      } else if (view.querySelector(':scope > input[role="switch"]') !== null) {
        this.role = NativeGestureRole.Switch;
      }
    }
  }

  public override updateGestureConfig(config: Config): void {
    super.updateGestureConfig(config);

    if (config.shouldActivateOnStart !== undefined) {
      this.shouldActivateOnStart = config.shouldActivateOnStart;
    }
    if (config.disallowInterruption !== undefined) {
      this.disallowInterruption = config.disallowInterruption;
    }
    if (config.yieldsToContinuousGestures !== undefined) {
      this.yieldsToContinuousGestures = config.yieldsToContinuousGestures;
    }

    const view = this.delegate.view as HTMLElement;
    this.restoreViewStyles(view);
  }

  private restoreViewStyles(view: HTMLElement) {
    if (!view) {
      return;
    }

    view.style['touchAction'] = 'auto';
    // @ts-ignore Turns on default touch behavior on Safari
    view.style['WebkitTouchCallout'] = 'auto';
  }

  protected override onPointerDown(event: AdaptedEvent): void {
    this.tracker.addToTracker(event);
    super.onPointerDown(event);
    this.newPointerAction();
  }

  protected override onPointerAdd(event: AdaptedEvent): void {
    this.tracker.addToTracker(event);
    super.onPointerAdd(event);
    this.newPointerAction();
  }

  private newPointerAction(): void {
    const lastCoords = this.tracker.getAbsoluteCoordsAverage();
    this.startX = lastCoords.x;
    this.startY = lastCoords.y;

    if (this.state !== State.UNDETERMINED) {
      return;
    }

    this.begin();

    dispatchGestureLifecycleEvent(
      this.delegate.view as HTMLElement | null,
      GestureLifecycleEvent.Began
    );

    const view = this.delegate.view as HTMLElement;
    const isRNGHText = view.hasAttribute('rnghtext');

    if (
      (this.role === NativeGestureRole.Button && this.shouldActivateOnStart) ||
      this.role === NativeGestureRole.Switch ||
      isRNGHText
    ) {
      this.activate();
    }
  }

  protected override onPointerMove(event: AdaptedEvent): void {
    this.tracker.track(event);

    const lastCoords = this.tracker.getAbsoluteCoordsAverage();
    const dx = this.startX - lastCoords.x;
    const dy = this.startY - lastCoords.y;
    const distSq = dx * dx + dy * dy;

    if (
      this.role === NativeGestureRole.Switch ||
      this.role === NativeGestureRole.Button
    ) {
      return;
    }

    if (distSq >= this.minDistSq && this.state === State.BEGAN) {
      this.activate();
    }
  }

  protected override onPointerLeave(): void {
    if (this.state === State.BEGAN || this.state === State.ACTIVE) {
      this.cancel();
    }
  }

  protected override onPointerUp(event: AdaptedEvent): void {
    super.onPointerUp(event);
    this.onUp(event);
  }

  protected override onPointerRemove(event: AdaptedEvent): void {
    super.onPointerRemove(event);
    this.onUp(event);
  }

  private onUp(event: AdaptedEvent): void {
    this.tracker.removeFromTracker(event.pointerId);

    if (this.tracker.trackedPointersCount === 0) {
      if (
        this.role === NativeGestureRole.Button &&
        this.state === State.BEGAN
      ) {
        this.activate();
      }

      if (this.state === State.ACTIVE) {
        this.end();
      } else {
        this.fail();
      }
    }
  }

  public override shouldRecognizeSimultaneously(
    handler: IGestureHandler
  ): boolean {
    if (super.shouldRecognizeSimultaneously(handler)) {
      return true;
    }

    if (
      handler instanceof NativeViewGestureHandler &&
      handler.state === State.ACTIVE &&
      handler.disallowsInterruption() &&
      !handler.yieldsToContinuousGestures
    ) {
      return false;
    }

    const canBeInterrupted =
      !this.disallowInterruption ||
      (this.yieldsToContinuousGestures && handler.isContinuous);

    if (
      this.state === State.ACTIVE &&
      handler.state === State.ACTIVE &&
      canBeInterrupted
    ) {
      return false;
    }

    return (
      this.state === State.ACTIVE && canBeInterrupted && handler.handlerTag > 0
    );
  }

  public override detach(): void {
    super.detach();
    this.role = null;
  }

  public override shouldBeCancelledByOther(handler: IGestureHandler): boolean {
    return (
      !this.disallowInterruption ||
      (this.yieldsToContinuousGestures && handler.isContinuous)
    );
  }

  public override shouldAttachGestureToChildView(): boolean {
    return true;
  }

  public disallowsInterruption(): boolean {
    return this.disallowInterruption;
  }

  public isButton(): boolean {
    return this.role === NativeGestureRole.Button;
  }

  public override shouldBeginWithRecordedHandlers(
    recorded: IGestureHandler[]
  ): boolean {
    if (!this.isButton()) {
      return true;
    }

    const self = this as IGestureHandler;
    return recorded.every(
      (other) =>
        other.shouldRecognizeSimultaneously(self) ||
        self.shouldRecognizeSimultaneously(other) ||
        other.delegate.view === this.delegate.view ||
        other.name === SingleGestureName.Hover
    );
  }

  protected override onCancel(): void {
    super.onCancel();
    dispatchGestureLifecycleEvent(
      this.delegate.view as HTMLElement | null,
      GestureLifecycleEvent.Canceled
    );
  }

  protected override transformNativeEvent(): Record<string, unknown> {
    return {
      pointerInside: this.delegate.isPointerInBounds(
        this.tracker.getAbsoluteCoordsAverage()
      ),
    };
  }

  protected override shouldSuppressActiveUpdate(
    handlerData: HandlerData<NativeHandlerData>
  ): boolean {
    if (
      this.lastActiveHandlerData &&
      deepEqual(this.lastActiveHandlerData, handlerData)
    ) {
      return true;
    }
    this.lastActiveHandlerData = handlerData;
    return false;
  }

  public override reset(): void {
    super.reset();
    this.lastActiveHandlerData = null;
  }
}
