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

import type {Doc} from 'yjs';

import devInvariant from '@lexical/internal/devInvariant';
import {createContext, useContext} from 'react';

/**
 * The value stored in the {@link CollaborationContext}: the local user's
 * display `name` and cursor `color`, whether collaboration is currently active,
 * and the map of Yjs documents shared by the editors under this provider.
 */
export type CollaborationContextType = {
  color: string;
  isCollabActive: boolean;
  name: string;
  yjsDocMap: Map<string, Doc>;
};

const entries = [
  ['Cat', 'rgb(125, 50, 0)'],
  ['Dog', 'rgb(100, 0, 0)'],
  ['Rabbit', 'rgb(150, 0, 0)'],
  ['Frog', 'rgb(200, 0, 0)'],
  ['Fox', 'rgb(200, 75, 0)'],
  ['Hedgehog', 'rgb(0, 75, 0)'],
  ['Pigeon', 'rgb(0, 125, 0)'],
  ['Squirrel', 'rgb(75, 100, 0)'],
  ['Bear', 'rgb(125, 100, 0)'],
  ['Tiger', 'rgb(0, 0, 150)'],
  ['Leopard', 'rgb(0, 0, 200)'],
  ['Zebra', 'rgb(0, 0, 250)'],
  ['Wolf', 'rgb(0, 100, 150)'],
  ['Owl', 'rgb(0, 100, 100)'],
  ['Gull', 'rgb(100, 0, 100)'],
  ['Squid', 'rgb(150, 0, 150)'],
];

// Picked on first use rather than at module scope, where the `Math.random()`
// call is a side effect to bundlers that would pin this module (and the
// table above) into every bundle importing it. It is still picked once and
// shared by every context created afterwards, as before.
let randomEntry: undefined | (typeof entries)[number];
function getRandomEntry(): (typeof entries)[number] {
  if (randomEntry === undefined) {
    randomEntry = entries[Math.floor(Math.random() * entries.length)];
  }
  return randomEntry;
}

/**
 * The React context that holds the shared {@link CollaborationContextType} for
 * collaborative editors. Provide it with {@link LexicalCollaboration} and read
 * it with {@link useCollaborationContext}.
 */
export const CollaborationContext =
  // Annotated by hand: React's createContext is not a Lexical factory, so the
  // build does not annotate it, and an unannotated module-scope call pins the
  // module into every bundle.
  /* @__PURE__ */ createContext<CollaborationContextType | null>(null);

/**
 * @internal
 * @__NO_SIDE_EFFECTS__
 */
export function newContext(): CollaborationContextType {
  const [name, color] = getRandomEntry();
  return {
    color,
    isCollabActive: false,
    name,
    yjsDocMap: new Map(),
  };
}

// This is here to help the transition post-#7818, however should be removed in a future release as
// a shared context across editors is likely to lead to bugs.
const UNSAFE_GLOBAL_CONTEXT = newContext();

/**
 * Reads the current {@link CollaborationContextType} from the nearest
 * {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
 * to set the local user's display name and cursor color.
 *
 * @returns The active collaboration context.
 */
export function useCollaborationContext(
  username?: string,
  color?: string,
): CollaborationContextType {
  let collabContext = useContext(CollaborationContext);
  devInvariant(
    collabContext != null,
    'useCollaborationContext: no context provider found',
  );

  collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;

  if (username != null) {
    // eslint-disable-next-line react-hooks/immutability
    collabContext.name = username;
  }

  if (color != null) {
    // eslint-disable-next-line react-hooks/immutability
    collabContext.color = color;
  }

  return collabContext;
}
