import {BreakpointFnParam, CanvasBreakpoints} from './breakpoints';

/**
 * A single palette within a Canvas theme
 */

export type CanvasThemePalette = {
  lightest: string;
  lighter: string;
  light: string;
  main: string;
  dark: string;
  darkest: string;
  contrast: string;
};

type CanvasThemeCommonPalette = {
  focusOutline: string;
  alertInner: string;
  alertOuter: string;
  errorInner: string;
};

/**
 * Direction of page content for internationalization
 * @deprecated ⚠️ `ContentDirection` is deprecated. Use the `:dir()` CSS pseudo-class selector and CSS logical properties instead. For more information, see [MDN :dir()](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) and [CSS Logical Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values).
 */
export enum ContentDirection {
  LTR = 'ltr',
  RTL = 'rtl',
}

/**
 * The format of a Canvas theme for components that support it.
 * @deprecated ⚠️ `CanvasTheme` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
 */
export interface CanvasTheme {
  palette: {
    common: CanvasThemeCommonPalette;
    primary: CanvasThemePalette;
    error: CanvasThemePalette;
    alert: CanvasThemePalette;
    success: CanvasThemePalette;
    neutral: CanvasThemePalette;
  };
  /**
   * ### Theme Breakpoints
   *
   * Breakpoints are used by media queries to conditionally apply or modify styles based on the viewport width.
   * This allows the UI to be responsive to various screen sizes.
   *
   * This breakpoints object contains `values` and several helper functions: `up`, `down`, `between`, and `only`.
   * You can find more detailed information by inspecting individual values and functions.
   */
  breakpoints: {
    /**
     * ### Breakpoint Values
     *
     * This object provides five breakpoint values
     * that correspond to the min-widths of our standard screen sizes.
     *
     * - `zero`: 0
     * - `s`: 320
     * - `m`: 768
     * - `l`: 1024
     * - `xl`: 1440
     *
     * And these are our standard screen size ranges:
     *
     * - `small` (320px - 767px) Used for mobile-sized screens
     * - `medium` (768px - 1023px) Used for tablet-sized screens
     * - `large` - (1024px - 1439px) Used for laptop and small desktop screens
     * - `extra-large` (≥1440px) Used for very large screens
     *
     * Note: Some applications may only require a subset of screen sizes and not use all breakpoints.
     *
     */
    values: CanvasBreakpoints;
    /**
     * ### Up
     *
     * _Returns a media query above the `min-width` for the range of a given breakpoint_
     *
     * Given a `start` breakpoint key ("zero", "s", "m", "l", "xl"),
     * this function returns a media query (string) using a `min-width`.
     *
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { up } = theme.canvas.breakpoints;
     * const mediaQuery = up('l'); // Returns '@media (min-width: 1024px)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.m,
     *   }
     * };
     * ```
     */
    up: (key: BreakpointFnParam) => string;
    /**
     * ### Down
     *
     * _Returns a media query below the `max-width` for the range of a given breakpoint_
     *
     * Given an `end` breakpoint key ("zero", "s", "m", "l", "xl"),
     * this function returns a media query (string) using a `max-width`.
     *
     * Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions.
     * For example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
     *
     * If the `xl` breakpoint is provided, this function returns a media query with only a `min-width` of `0`,
     * as seen in the second example below.
     *
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { down } = theme.canvas.breakpoints;
     * const mediaQuery = down('m'); // Returns '@media (max-width: 1023.5px)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.m,
     *   }
     * };
     * ```
     *
     * This example uses the `xl` breakpoint and only adds a `min-width` of `0` to the media query.
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { down } = theme.canvas.breakpoints;
     * const mediaQuery = down('xl'); // Returns '@media (min-width: 0)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.m,
     *   }
     * };
     * ```
     */
    down: (key: BreakpointFnParam) => string;
    /**
     * ### Between
     *
     * _Returns a media query between two given breakpoints_
     *
     * Given `start` and `end` breakpoint keys ("zero", "s", "m", "l", "xl"),
     * this function returns a media query (string) using a min-width and max-width.
     *
     * Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions.
     * For example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
     *
     * If the `xl` breakpoint is provided, this function returns a media query with only a `min-width`,
     * as seen in the second example below.
     *
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { between } = theme.canvas.breakpoints;
     * // Returns '@media (min-width: 320px) and (max-width: 1023.5px)'
     * const mediaQuery = between('s', 'm');
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.s,
     *   }
     * };
     * ```
     *
     * This example uses `xl` as the `end` breakpoint and only adds a min-width to the media query.
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { between } = theme.canvas.breakpoints;
     * const mediaQuery = between('m', 'xl'); // Returns '@media (min-width: 768px)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.s,
     *   }
     * };
     * ```
     */
    between: (start: BreakpointFnParam, end: BreakpointFnParam) => string;
    /**
     * ### Only
     *
     * _Returns a media query with a `min-width` and `max-width` for a given breakpoint_
     *
     * Given a breakpoint key ("zero", "s", "m", "l", "xl"),
     * this function returns a media query (string) using a `min-width` and `max-width`.
     *
     * Note: This function subtracts `0.5px` from the next breakpoint value to prevent collisions.
     * For example, `breakpoints.values.s`, has a `min-width` of `320px`, and the `max-width` is `767.5px`).
     *
     * If the `xl` breakpoint is provided, this function returns a media query with only a `min-width` of `1440px`,
     * as seen in the second example below.
     *
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { only } = theme.canvas.breakpoints;
     * const mediaQuery = only('s'); // Returns '@media (min-width: 320px) and (max-width: 767.5px)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.s,
     *   }
     * };
     * ```
     *
     * This example uses the `xl` breakpoint and only adds a `min-width` of `1440px` to the media query.
     * @example
     * ```ts
     * import { useTheme } from '@workday/canvas-kit-react/common';
     * import { space } from '@workday/canvas-kit-react/tokens';
     *
     * const theme = useTheme();
     * const { only } = theme.canvas.breakpoints;
     * const mediaQuery = only('xl'); // Returns '@media (min-width: 1440px)'
     * const styles = {
     *   [mediaQuery]: {
     *     padding: space.s,
     *   }
     * };
     * ```
     */
    only: (key: BreakpointFnParam) => string;
  };
  direction: ContentDirection;
}

/**
 * Indicates a component is themeable with a CanvasTheme
 * @deprecated `Themeable` is deprecated. If you want to theme your application, please use `<CanvasProvider theme={{canvas: {palette: {primary: {main: 'orange'}}}}} />` at the root of your application or use our CSS tokens to change individual component styles as seen in our [Button docs](https://workday.github.io/canvas-kit/?path=/docs/components-buttons--docs#custom-styles).
 */
export interface Themeable {
  theme?: EmotionCanvasTheme;
}

/**
 * For custom themes that do not overwrite every default.
 * @deprecated ⚠️ `RecursivePartial` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
 */
type RecursivePartial<T> = {
  [P in keyof T]?: RecursivePartial<T[P]>;
};

/**
 * @deprecated ⚠️ `PartialCanvasTheme` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
 */
export type PartialCanvasTheme = RecursivePartial<CanvasTheme>;

/**
 * @deprecated ⚠️ `PartialCanvasThemePalette` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
 */
export type PartialCanvasThemePalette = RecursivePartial<CanvasThemePalette>;

export type PartialEmotionCanvasTheme = {canvas?: PartialCanvasTheme};

declare module '@emotion/react' {
  export interface Theme {
    canvas: CanvasTheme;
  }
}

/**
 * @deprecated ⚠️ `EmotionCanvasTheme` is deprecated. In previous versions of Canvas Kit, we allowed teams to pass a theme object, this supported [Emotion's theming](https://emotion.sh/docs/theming). Now that we're shifting to a global theming approach based on CSS variables, we advise to no longer using the theme prop. For more information, view our [Theming Docs](https://workday.github.io/canvas-kit/?path=/docs/features-theming-overview--docs#-preferred-approach-v14).
 */
export type EmotionCanvasTheme = {canvas: CanvasTheme};

/**
 * Numerical brand ramp keys. Each key maps 1:1 to a `--cnvs-brand-{family}-{key}` CSS variable.
 *
 * Common keys:
 * - `'600'` — main accent / button fill / default brand fg
 * - `'700'` — strong fg / selected text (when not using `selected.fg`)
 * - `'500'` — mid primary ramp (does **not** set focus — use {@link CanvasNumericalBrandTheme.focus})
 * - `'A50'` — selected surface tint (when not using `selected.surface`)
 * - `'25'` / `'A25'` — subtle brand surfaces
 *
 * Neutral-only Sana steps (`'150'`, `'850'`, `'A150'`) live on {@link CanvasNeutralBrandRamp}.
 */
export type CanvasBrandRamp = Partial<
  Record<
    | '25'
    | '50'
    | '100'
    | '200'
    | '300'
    | '400'
    | '500'
    | '600'
    | '700'
    | '800'
    | '900'
    | '950'
    | '975'
    | 'A25'
    | 'A50'
    | 'A100'
    | 'A200'
    | 'A300',
    string
  >
>;

/**
 * Neutral brand ramp — includes Sana-only steps (`150` / `850` / `A150` / `A850`), plus the
 * extended alpha steps (`A400`–`A975`) that only the neutral family exposes.
 */
export type CanvasNeutralBrandRamp = CanvasBrandRamp &
  Partial<
    Record<
      | '150'
      | '850'
      | 'A150'
      | 'A400'
      | 'A500'
      | 'A600'
      | 'A700'
      | 'A800'
      | 'A850'
      | 'A900'
      | 'A950'
      | 'A975',
      string
    >
  >;

/** Semantic keys for `brand.action.*` CSS variables (PrimaryButton, etc.). */
export type CanvasActionBrandRamp = Partial<
  Record<
    'base' | 'lightest' | 'lighter' | 'light' | 'dark' | 'darkest' | 'darker' | 'accent',
    string
  >
>;

/**
 * Controls how partial theme input is expanded.
 *
 * - `'brand'` (default): predictable, design-aligned behavior. Setting only
 *   `brand.primary['600']` themes PrimaryButton and selected list/menu
 *   states. Other keys write only their CSS variable — no auto-generated ramps.
 * - `'full'`: legacy behavior for the deprecated `canvas.palette` shape — auto-fills
 *   lightest→darkest via `shiftColor` and forwards to many `system.color.brand.*`
 *   tokens. On the numerical `brand` shape, `'full'` disables the primary shortcut
 *   and writes each ramp key literally.
 */
export type CanvasThemingScope = 'brand' | 'full';

/**
 * Preferred theme input for `CanvasProvider`. Each value maps directly to brand CSS
 * variables unless noted as a shortcut below.
 *
 * @example Minimal — brand buttons + selected states only
 * ```tsx
 * <CanvasProvider theme={{brand: {primary: {'600': base.magenta600}}}} />
 * ```
 *
 * @example Explicit — focus independent of primary
 * ```tsx
 * <CanvasProvider theme={{
 *   brand: {primary: {'600': base.red600}},
 *   selected: {fg: base.red700, surface: base.redA50},
 *   focus: {primary: base.blue500},
 * }} />
 * ```
 *
 * @see sanaCanvasProviderTheme when `<html>` is unavailable and popups need Sana brand forwarding
 */
export interface CanvasNumericalBrandTheme {
  brand?: {
    /**
     * Primary brand ramp (`--cnvs-brand-primary-*`).
     *
     * **Shortcut (brand scope only):** when `'600'` is the only key under `primary`,
     * also sets:
     * - `PrimaryButton` — `brand.action.base`, `accent.primary`, `accent.action`
     * - Selected `Menu.Item` — `system.color.brand.fg.selected`, `surface.selected`
     *
     * Does **not** set focus rings — use {@link CanvasNumericalBrandTheme.focus}
     * or legacy `canvas.palette.common.focusOutline`.
     *
     * | Key | CSS variable | Typical consumers |
     * |-----|--------------|-------------------|
     * | `'600'` | `--cnvs-brand-primary-600` | PrimaryButton, brand links, accent.primary |
     * | `'700'` | `--cnvs-brand-primary-700` | Strong primary fg, selected text |
     * | `'500'` | `--cnvs-brand-primary-500` | Mid primary ramp only (not focus) |
     * | `'A50'` | `--cnvs-brand-primary-A50` | Selected/hover surfaces |
     */
    primary?: CanvasBrandRamp;

    /**
     * Button-specific ramp (`--cnvs-brand-action-*`). PrimaryButton reads
     * these **before** `brand.primary`.
     *
     * | Key | Typical consumers |
     * |-----|-------------------|
     * | `base` | PrimaryButton background |
     * | `dark` / `darkest` | PrimaryButton hover / pressed |
     * | `accent` | PrimaryButton label color |
     */
    action?: CanvasActionBrandRamp;

    /**
     * Critical/error ramp (`--cnvs-brand-critical-*`).
     *
     * | Key | Typical consumers |
     * |-----|-------------------|
     * | `'600'` | TextInput error, DeleteButton, critical fg |
     * | `'500'` | Critical focus ring, error border |
     * | `'A25'` / `'A50'` | Error surface tints |
     */
    critical?: CanvasBrandRamp;

    /**
     * Caution/warning ramp (`--cnvs-brand-caution-*`).
     *
     * | Key | Typical consumers |
     * |-----|-------------------|
     * | `'400'` | Caution accent, TextInput caution |
     * | `'500'` | Caution focus outer, caution border |
     * | `'A25'` / `'A50'` | Caution surface tints |
     */
    caution?: CanvasBrandRamp;

    /**
     * Positive/success ramp (`--cnvs-brand-positive-*`).
     *
     * | Key | Typical consumers |
     * |-----|-------------------|
     * | `'600'` | Checkbox/Radio checked, success fg |
     * | `'A25'` / `'A50'` | Success surface tints |
     */
    positive?: CanvasBrandRamp;

    /**
     * Neutral brand ramp (`--cnvs-brand-neutral-*`). Sana Canvas uses `base.neutral*`
     * instead of legacy `base.slate*` — see {@link sanaCanvasNumericalTheme}.
     *
     * Affects brand-neutral text, borders, and surfaces where components reference
     * `brand.neutral.*` or `system.color.brand` tokens tied to neutral.
     */
    neutral?: CanvasNeutralBrandRamp;
  };

  /**
   * Selected-state shortcuts. Prefer these over indirect `brand.primary['700']` /
   * `brand.primary.A50` when customizing list/menu selection.
   *
   * | Key | CSS variable | Typical consumers |
   * |-----|--------------|-------------------|
   * | `fg` | `--cnvs-sys-color-brand-fg-selected` | Menu.Item, SegmentedControl selected text |
   * | `surface` | `--cnvs-sys-color-brand-surface-selected` | Menu.Item, list selected background |
   */
  selected?: {
    /** Selected text/icon color */
    fg?: string;
    /** Selected row/chip background */
    surface?: string;
  };

  /**
   * Focus-ring shortcuts. Independent of `brand.primary` — matches tokens where
   * `--cnvs-sys-color-brand-focus-primary` is not derived from primary brand.
   *
   * | Key | CSS variable | Typical consumers |
   * |-----|--------------|-------------------|
   * | `primary` | `--cnvs-sys-color-brand-focus-primary` (+ border primary) | Menu, Button, input focus rings |
   */
  focus?: {
    /** Focus ring and primary border color */
    primary?: string;
  };

  /**
   * Escape hatch for `system.color.brand.*` tokens not covered by `brand` ramps.
   * Keys mirror the token path, e.g. `{color: {brand: {focus: {primary: '#00f'}}}}`.
   */
  system?: {
    color?: {
      brand?: Record<string, unknown>;
    };
  };

  /** Text direction for the provider subtree. */
  direction?: ContentDirection;

  /**
   * @default 'brand'
   * @see CanvasThemingScope
   */
  themeScope?: CanvasThemingScope;
}

/**
 * Theme input accepted by {@link CanvasProvider}.
 * - Numerical `brand` shape (preferred)
 * - Deprecated `canvas.palette` shape (legacy)
 */
export type CanvasProviderTheme =
  | (PartialEmotionCanvasTheme & {themeScope?: CanvasThemingScope})
  | CanvasNumericalBrandTheme;

export function isNumericalTheme(
  theme: CanvasProviderTheme | undefined
): theme is CanvasNumericalBrandTheme {
  if (!theme) {
    return false;
  }
  if ('canvas' in theme) {
    return false;
  }
  // `direction` / `themeScope` alone are valid numerical themes (e.g. RTL-only).
  return (
    'brand' in theme ||
    'system' in theme ||
    'selected' in theme ||
    'focus' in theme ||
    'direction' in theme ||
    'themeScope' in theme
  );
}

const EXTENDED_RAMP_KEYS = new Set([
  '25',
  '50',
  '100',
  '200',
  '300',
  '400',
  '500',
  '600',
  '700',
  '800',
  '900',
  '950',
  '975',
  'A25',
  'A50',
  'A100',
  'A200',
  'lightest',
  'lighter',
  'light',
  'main',
  'dark',
  'darkest',
  'contrast',
]);

export function resolveThemingScope(theme: CanvasProviderTheme | undefined): CanvasThemingScope {
  if (!theme) {
    return 'brand';
  }
  if (theme.themeScope) {
    return theme.themeScope;
  }

  if (isNumericalTheme(theme)) {
    return theme.themeScope ?? 'brand';
  }

  const palette = theme.canvas?.palette;
  if (!palette) {
    return 'brand';
  }

  if (palette.common && Object.keys(palette.common).length > 0) {
    const commonOnlyFocus =
      Object.keys(palette.common).length === 1 && palette.common.focusOutline != null;
    if (!commonOnlyFocus) {
      return 'full';
    }
  }

  for (const color of ['primary', 'error', 'alert', 'success', 'neutral'] as const) {
    const colorPalette = palette[color];
    if (!colorPalette) {
      continue;
    }
    for (const key of Object.keys(colorPalette)) {
      // `main` alone uses brand-scope bundles for every semantic palette color.
      if (key === 'main') {
        continue;
      }
      if (EXTENDED_RAMP_KEYS.has(key)) {
        return 'full';
      }
    }
  }

  return 'brand';
}
