import { isNil } from 'lodash-es';

import { type MeUser, type User } from 'src/types/user';
import { addRemoveDay, simpleDateStringToLocalDate } from 'src/utils/date';
import { type LokaliseTFunction } from 'src/utils/i18n';

import { CycleValues } from './constants';
import {
  type Goal,
  type GoalCycle,
  GoalCycleProgressController,
  type GoalCycleProgressControllerValue,
  GoalCycleStatus,
  GoalProgressTypes,
  GoalStatus,
  type IndividualGoal,
} from './types';

const SOUTHBAY_ID = 80594;
const TISSUR_ID = 8772;

export const goalVisualProgress = (progress: number) =>
  progress > 100 ? 100 : progress;

export const getTotal = (total: number): string | number => {
  if (total > 999) return '+999';
  if (total < -999) return '-999';
  return total;
};

/** DB/cycle instants → calendar day as stored in UTC, for MUI date pickers. */
export const utcCalendarDayToFormDate = (d: string | Date): Date =>
  simpleDateStringToLocalDate(typeof d === 'string' ? d : d.toISOString());

/** MUI `minDate` for goal end picker: day after form start, or after cycle start if unset. */
export const getGoalEndDatePickerMinDate = (
  startDate: Date | null | undefined,
  cycleStart: Date | undefined,
): Date | undefined => {
  if (startDate) {
    return new Date(addRemoveDay(startDate.toISOString()));
  }
  if (cycleStart) {
    return new Date(addRemoveDay(cycleStart.toISOString()));
  }
  return undefined;
};

export const isGoalCycleFinishedStatus = (status?: GoalCycleStatus): boolean =>
  status === GoalCycleStatus.FINISHED;

export const isGoalCycleInProgressStatus = (
  status?: GoalCycleStatus,
): boolean => status === GoalCycleStatus.IN_PROGRESS;

export const isGoalInProgressStatus = (status?: string): boolean =>
  status === GoalStatus.IN_PROGRESS;

export const isGoalFinishedStatus = (status?: string): boolean =>
  status === GoalStatus.FINISHED;

export const isGoalDraftStatus = (status?: string): boolean =>
  status === GoalStatus.DRAFT;

/**
 * Whether the user can log progress on a goal, per the cycle's
 * `progressControlConfig`: 'OWNER' allows the goal owner, 'BOSS' allows the
 * owner's direct boss, and 'ADMIN' is backoffice-only (ignored here).
 * A nullish config (legacy/cached cycles) falls back to owner OR boss.
 */
export const canLogProgressByConfig = (
  progressControlConfig: GoalCycleProgressControllerValue[] | null | undefined,
  { isOwner, isBoss }: { isOwner: boolean; isBoss: boolean },
): boolean => {
  if (isNil(progressControlConfig)) return isOwner || isBoss;
  return (
    (isOwner &&
      progressControlConfig.includes(GoalCycleProgressController.OWNER)) ||
    (isBoss && progressControlConfig.includes(GoalCycleProgressController.BOSS))
  );
};

export type GetOptionDisabledForResponsibleParams = {
  isNewGoal: boolean;
  currentResponsible: User | User[];
  option: User;
  loggedUser: MeUser;
};

/**
 * Determines if a user option should be disabled in the responsible user selector.
 *
 * This function is used to control which users can be selected as responsible(s) when creating or editing a goal.
 *
 * - When creating a new goal (isNewGoal = true):
 *   - If the logged-in user is already selected, only the logged-in user remains selectable (others are disabled).
 *   - If any collaborator (not the logged-in user) is selected, the logged-in user is disabled.
 *   - Otherwise, all users are selectable.
 * - When editing a goal (isNewGoal = false):
 *   - All users are selectable (no options are disabled).
 *
 * @param {GetOptionDisabledForResponsibleParams} params - The parameters for the function.
 * @param {boolean} params.isNewGoal - Whether the goal is being created (true) or edited (false).
 * @param {User | User[]} params.currentResponsible - The current value of the responsible field (array or single user).
 * @param {User} params.option - The user option being evaluated.
 * @param {User} params.loggedUser - The currently logged-in user.
 * @returns {boolean} true if the option should be disabled, false otherwise.
 */
export const isResponsibleOptionDisabled = ({
  isNewGoal: isNewGoalParam,
  currentResponsible,
  option,
  loggedUser,
}: GetOptionDisabledForResponsibleParams): boolean => {
  if (!isNewGoalParam) return false;

  const values = Array.isArray(currentResponsible)
    ? currentResponsible
    : [currentResponsible];

  const hasLoggedUser = values.some(v => v?.id === loggedUser?.id);
  if (hasLoggedUser) {
    return option.id !== loggedUser.id;
  }

  const hasCollaborators = values.some(v => v?.id !== loggedUser?.id);
  if (hasCollaborators) {
    return option.id === loggedUser.id;
  }

  return false;
};

/**
 * Type guard to check if a value is neither null nor undefined.
 *
 * Useful for filtering out nullish values (null or undefined) from arrays or when you want to ensure a value is present.
 *
 * @template T - The type of the value being checked.
 * @param {T} x - The value to check.
 * @returns {x is NonNullable<T>} True if x is not null or undefined, false otherwise.
 */
export const nonNullable = <T>(x: T): x is NonNullable<T> =>
  x !== null && x !== undefined;

/**
 * Type guard to check if a value is not an empty string after trimming whitespace.
 */
export const nonEmptyString = (value: unknown): value is string =>
  String(value).trim() !== '';

/**
 * Checks if the current instance is TISSUR.
 * @param {number | undefined | null} instanceId - The instance ID to check.
 * @param {number} [TISSUR_ID=8772] - The TISSUR instance ID constant.
 * @returns {boolean} True if the instance is TISSUR, false otherwise.
 */
export const isTissurInstance = (
  instanceId: number | undefined | null,
  tissurId = TISSUR_ID,
): boolean => {
  return instanceId === tissurId;
};

/**
 * Checks if the value type is binary.
 * @param {GoalProgressTypes | undefined | null} valueType - The value type to check.
 * @returns {boolean} True if the value type is binary, false otherwise.
 */
export const isBinaryProgressType = (
  valueType: GoalProgressTypes | undefined | null,
): boolean => {
  return valueType === GoalProgressTypes.BINARY;
};

/**
 * Checks if the value type is number.
 * @param {GoalProgressTypes | undefined | null} valueType - The value type to check.
 * @returns {boolean} True if the value type is number, false otherwise.
 */
export const isNumberProgressType = (
  valueType: GoalProgressTypes | undefined | null,
): boolean => {
  return valueType === GoalProgressTypes.NUMBER;
};

/**
 * Checks if the goal is new (i.e., does not have an ID).
 * @param {number | string | undefined | null} goalId - The goal ID to check.
 * @returns {boolean} True if the goal is new, false otherwise.
 */
export const isGoalNew = (
  goalId: number | string | undefined | null,
): boolean => {
  return !goalId;
};

/**
 * Checks if the goal is being edited (i.e., has an ID).
 * @param {number | string | undefined | null} goalId - The goal ID to check.
 * @returns {boolean} True if the goal is being edited, false otherwise.
 */
/**
 * Checks if the current instance is SOUTHBAY.
 * @param {number | undefined | null} instanceId - The instance ID to check.
 * @param {number} [southbayId=SOUTHBAY_ID] - The SOUTHBAY instance ID constant.
 * @returns {boolean} True if the instance is SOUTHBAY, false otherwise.
 */
export const isSouthbayInstance = (
  instanceId: number | undefined | null,
  southbayId = SOUTHBAY_ID,
): boolean => {
  return instanceId === southbayId;
};

export const getNoSaveModalText = (
  // biome-ignore lint/suspicious/noExplicitAny: out of scope
  responsible: any,
  loggedUser: MeUser | null,
  t: LokaliseTFunction,
) => {
  if (!nonNullable(responsible) || !nonNullable(loggedUser)) {
    return '';
  }

  if (!Array.isArray(responsible)) {
    return loggedUser.id === responsible.id
      ? t('new.no_save_modal_self_description')
      : t('new.no_save_modal_description', {
          name: `${responsible?.firstName} ${responsible?.lastName}`,
        });
  }

  if (responsible.length === 1) {
    return loggedUser.id === responsible[0].id
      ? t('new.no_save_modal_self_description')
      : t('new.no_save_modal_description', {
          name: `${responsible[0]?.firstName} ${responsible[0]?.lastName}`,
        });
  } else return t('new.no_save_modal_description_multiple_owners');
};

/**
 * Picks the most recent cycle by endDate (descending).
 * If dates are equal, falls back to the one with the later startDate.
 * Returns null if the list is empty.
 */
export const getMostRecentCycleId = (cycles: GoalCycle[]): number | null => {
  if (!cycles || cycles.length === 0) return null;

  const sorted = cycles.toSorted((a, b) => {
    const endDiff =
      new Date(b.endDate).getTime() - new Date(a.endDate).getTime();
    if (endDiff !== 0) return endDiff;
    return new Date(b.startDate).getTime() - new Date(a.startDate).getTime();
  });

  return sorted[0].id;
};

/**
 * Gets the default goal cycle ID based on navigation state and options.
 *
 * @param goalCycleIdFromState - The goal cycle ID from navigation state.
 * @param showAllAndUnassignedOptions - Whether to show "All" and "Unassigned" options.
 * @param cycles - Full list of goal cycles (used to pick the most recent when not showing all).
 * @returns The default goal cycle ID to use.
 */
export const getDefaultGoalCycleId = (
  goalCycleIdFromState: number | undefined,
  showAllAndUnassignedOptions: boolean,
  cycles: GoalCycle[],
): number => {
  if (goalCycleIdFromState) {
    return goalCycleIdFromState;
  }
  if (showAllAndUnassignedOptions) return CycleValues.ALL_CYCLES;
  return getMostRecentCycleId(cycles) ?? 0;
};

/**
 * Regex that removes any HTML tag (e.g. <p>, <br>, <div>, etc).
 */
const HTML_TAG_REGEX = /<[^>]+>/g;
/**
 * Regex that replaces common non-breaking spaces (&nbsp; or unicode NBSP).
 */
const NBSP_REGEX = /(&nbsp;|\u00A0)/gi;
/**
 * Normalizes rich text description for API payload.
 *
 * Treats the following as "no description":
 * - null / undefined
 * - empty string
 * - whitespace-only
 * - HTML-only-empty content (e.g. "<p></p>", "<p><br></p>")
 *
 * @param value - Raw description from form (e.g. Rich Text Editor output).
 * @returns undefined when content is effectively empty; otherwise trimmed string.
 */
/** Maps API `IndividualGoal` rows to `Goal` with weight for team / weights UI. */
export const transformIndividualGoalsToGoals = (
  goalWeights: IndividualGoal[],
): Goal[] =>
  goalWeights.map(({ goal, weight }) => ({
    ...goal,
    weight,
  }));

/** Default values for react-hook-form fields `weight-{goalId}` in the weights drawer. */
export const buildGoalWeightFieldDefaults = (
  goals: Goal[],
): Record<string, number> => {
  if (!goals?.length) return {};
  const defaultValues: Record<string, number> = {};
  for (const goal of goals) {
    defaultValues[`weight-${goal.id}`] = goal.weight || 0;
  }
  return defaultValues;
};

export const normalizeDescription = (
  value: string | null | undefined,
): string | undefined => {
  if (isNil(value)) return undefined;
  const trimmed = String(value).trim();
  if (trimmed === '') return undefined;
  const textOnly = trimmed
    .replace(HTML_TAG_REGEX, '')
    .replace(NBSP_REGEX, ' ')
    .trim();
  if (textOnly === '') return undefined;
  return value;
};

export const getValueSymbol = (value: GoalProgressTypes) => {
  switch (value) {
    case GoalProgressTypes.MONETARY:
      return '$';
    case GoalProgressTypes.PERCENTAGE:
      return '%';
    default:
      return '';
  }
};
