/**
 * 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.
 *
 * @flow
 */

import type {TransitionTypes} from 'react/src/ReactTransitionType';
import type {
  Instance,
  Props,
  Container,
  SuspendedState,
  GestureTimeline,
} from './ReactFiberConfigFabric';

const {
  applyViewTransitionName: fabricApplyViewTransitionName,
  startViewTransition: fabricStartViewTransition,
} = nativeFabricUIManager;

export type InstanceMeasurement = {
  rect: {x: number, y: number, width: number, height: number},
  abs: boolean,
  clip: boolean,
  view: boolean,
};

export type RunningViewTransition = {
  finished: Promise<void>,
  ready: Promise<void>,
  ...
};

interface ViewTransitionPseudoElementType extends mixin$Animatable {
  _pseudo: string;
  _name: string;
}

function ViewTransitionPseudoElement(
  this: ViewTransitionPseudoElementType,
  pseudo: string,
  name: string,
) {
  // TODO: Get the owner document from the root container.
  this._pseudo = pseudo;
  this._name = name;
}

export type ViewTransitionInstance = null | {
  name: string,
  old: mixin$Animatable,
  new: mixin$Animatable,
  ...
};

export function restoreViewTransitionName(
  instance: Instance,
  props: Props,
): void {
  if (__DEV__) {
    console.warn('restoreViewTransitionName is not implemented');
  }
}

// Cancel the old and new snapshots of viewTransitionName
export function cancelViewTransitionName(
  instance: Instance,
  oldName: string,
  props: Props,
): void {
  if (__DEV__) {
    console.warn('cancelViewTransitionName is not implemented');
  }
}

export function cancelRootViewTransitionName(rootContainer: Container): void {
  // No-op
}

export function restoreRootViewTransitionName(rootContainer: Container): void {
  // No-op
}

export function cloneRootViewTransitionContainer(
  rootContainer: Container,
): Instance {
  if (__DEV__) {
    console.warn('cloneRootViewTransitionContainer is not implemented');
  }
  // $FlowFixMe[incompatible-return] Return empty stub
  return null;
}

export function removeRootViewTransitionClone(
  rootContainer: Container,
  clone: Instance,
): void {
  if (__DEV__) {
    console.warn('removeRootViewTransitionClone is not implemented');
  }
}

export function measureInstance(instance: Instance): InstanceMeasurement {
  if (__DEV__) {
    console.warn('measureInstance is not implemented');
  }
  return {
    rect: {
      x: 0,
      y: 0,
      width: 0,
      height: 0,
    },
    abs: false,
    clip: false,
    // TODO: properly calculate whether instance is in viewport
    view: true,
  };
}

export function measureClonedInstance(instance: Instance): InstanceMeasurement {
  if (__DEV__) {
    console.warn('measureClonedInstance is not implemented');
  }
  return {
    rect: {x: 0, y: 0, width: 0, height: 0},
    abs: false,
    clip: false,
    view: true,
  };
}

export function wasInstanceInViewport(
  measurement: InstanceMeasurement,
): boolean {
  return measurement.view;
}

export function hasInstanceChanged(
  oldMeasurement: InstanceMeasurement,
  newMeasurement: InstanceMeasurement,
): boolean {
  if (__DEV__) {
    console.warn('hasInstanceChanged is not implemented');
  }
  return false;
}

export function hasInstanceAffectedParent(
  oldMeasurement: InstanceMeasurement,
  newMeasurement: InstanceMeasurement,
): boolean {
  if (__DEV__) {
    console.warn('hasInstanceAffectedParent is not implemented');
  }
  return false;
}

export function startGestureTransition(
  suspendedState: null | SuspendedState,
  rootContainer: Container,
  timeline: GestureTimeline,
  rangeStart: number,
  rangeEnd: number,
  transitionTypes: null | TransitionTypes,
  mutationCallback: () => void,
  animateCallback: () => void,
  errorCallback: (error: mixed) => void,
  finishedAnimation: () => void,
): RunningViewTransition {
  if (__DEV__) {
    console.warn('startGestureTransition is not implemented');
  }
  return {
    finished: Promise.resolve(),
    ready: Promise.resolve(),
  };
}

export function stopViewTransition(transition: RunningViewTransition): void {
  if (__DEV__) {
    console.warn('stopViewTransition is not implemented');
  }
}

export function addViewTransitionFinishedListener(
  transition: RunningViewTransition,
  callback: () => void,
): void {
  transition.finished.finally(callback);
}

export function createViewTransitionInstance(
  name: string,
): ViewTransitionInstance {
  return {
    name,
    old: new (ViewTransitionPseudoElement: any)('old', name),
    new: new (ViewTransitionPseudoElement: any)('new', name),
  };
}

export function applyViewTransitionName(
  instance: Instance,
  name: string,
  className: ?string,
): void {
  // add view-transition-name to things that might animate for browser
  fabricApplyViewTransitionName(instance.node, name, className);
}

export function startViewTransition(
  suspendedState: null | SuspendedState,
  rootContainer: Container,
  transitionTypes: null | TransitionTypes,
  mutationCallback: () => void,
  layoutCallback: () => void,
  afterMutationCallback: () => void,
  spawnedWorkCallback: () => void,
  passiveCallback: () => mixed,
  errorCallback: (error: mixed) => void,
  blockedCallback: (name: string) => void,
  finishedAnimation: () => void,
): null | RunningViewTransition {
  const transition = fabricStartViewTransition(
    // mutation
    () => {
      mutationCallback(); // completeRoot should run here
      layoutCallback();
      afterMutationCallback();
    },
  );

  if (transition == null) {
    if (__DEV__) {
      console.warn(
        "startViewTransition didn't kick off transition in Fabric, the ViewTransition ReactNativeFeatureFlag might not be enabled.",
      );
    }
    // Flush remaining work synchronously.
    mutationCallback();
    layoutCallback();
    // Skip afterMutationCallback(). We don't need it since we're not animating.
    spawnedWorkCallback();
    // Skip passiveCallback(). Spawned work will schedule a task.
    return null;
  }

  transition.ready.then(() => {
    spawnedWorkCallback();
  });

  transition.finished.finally(() => {
    passiveCallback();
  });

  return transition;
}