import {StyleProp, StyleSheet, TextStyle} from 'react-native';
import {MixedStyleDeclaration} from 'react-native-render-html';
import {getTipTapTagStyles} from '@hooks/useHtmlTagStyles';
import {
  DEFAULT_BODY_FONT_SIZE,
  LINE_HEIGHT,
} from '@shared/utils/html/constants';

import {styles} from './styles';

type HtmlRenderTheme = Parameters<typeof getTipTapTagStyles>[0];

export interface SplitTextStyle {
  defaultBodyTextColor: TextStyle['color'];
  textStyleForDefaultProps: TextStyle | undefined;
}

/**
 * `defaultTextProps.style` is applied after tag styles; a global `color` there
 * overwrites `tagsStyles` for `code`/`a` and hides code text on a light
 * background in dark mode. We pull `color` out of `textStyle` and apply it
 * per-tag instead, while still forwarding the rest of the style.
 */
export const splitTextStyleColor = (
  textStyle: StyleProp<TextStyle> | undefined,
  fallbackColor: TextStyle['color'],
): SplitTextStyle => {
  if (!textStyle) {
    return {
      defaultBodyTextColor: fallbackColor,
      textStyleForDefaultProps: undefined,
    };
  }

  const flat = StyleSheet.flatten(textStyle);
  if (!flat || typeof flat !== 'object' || Object.keys(flat).length === 0) {
    return {
      defaultBodyTextColor: fallbackColor,
      textStyleForDefaultProps: undefined,
    };
  }

  const {color, ...rest} = flat;
  const hasRest = Object.keys(rest).length > 0;
  return {
    defaultBodyTextColor: color ?? fallbackColor,
    textStyleForDefaultProps: hasRest ? rest : undefined,
  };
};

export const buildBaseTagsStyles = (
  theme: HtmlRenderTheme,
  defaultBodyTextColor: TextStyle['color'],
): Record<string, MixedStyleDeclaration> => {
  const {
    blockquote,
    a,
    code,
    pre,
    li: tipTapLi,
    p: tipTapP,
    h1,
    h2,
    h3,
    h4,
  } = getTipTapTagStyles(theme);

  const listStyles: MixedStyleDeclaration = {
    color: defaultBodyTextColor,
    fontSize: DEFAULT_BODY_FONT_SIZE,
    lineHeight: LINE_HEIGHT,
  };

  /**
   * Default decoration tags to the themed body color so dark mode doesn't
   * fall back to a platform default. Don't pin `textDecorationColor`: RN
   * defaults it to `color`, so an inline `color` from the source HTML keeps
   * driving both the text and the underline/strike, instead of clashing
   * against a tag-level decoration color we'd otherwise force.
   */
  const underlineStyle: MixedStyleDeclaration = {
    color: defaultBodyTextColor,
    textDecorationLine: 'underline',
  };
  const strikeThroughStyle: MixedStyleDeclaration = {
    color: defaultBodyTextColor,
    textDecorationLine: 'line-through',
  };

  return {
    p: {
      ...tipTapP,
      ...styles.p,
      color: defaultBodyTextColor,
      lineHeight: LINE_HEIGHT,
    },
    h1: {...h1, color: defaultBodyTextColor},
    h2: {...h2, color: defaultBodyTextColor},
    h3: {...h3, color: defaultBodyTextColor},
    h4: {...h4, color: defaultBodyTextColor},
    blockquote: {...blockquote, color: defaultBodyTextColor},
    a,
    code,
    pre,
    ol: listStyles,
    ul: listStyles,
    li: {...tipTapLi, color: defaultBodyTextColor, lineHeight: LINE_HEIGHT},
    u: underlineStyle,
    ins: underlineStyle,
    s: strikeThroughStyle,
    del: strikeThroughStyle,
    strike: strikeThroughStyle,
  };
};

export const mergeTagsStyles = (
  base: Record<string, MixedStyleDeclaration>,
  overrides: Record<string, MixedStyleDeclaration> | undefined,
): Record<string, MixedStyleDeclaration> => {
  if (!overrides) {
    return base;
  }

  const allTags = new Set([...Object.keys(base), ...Object.keys(overrides)]);
  const merged: Record<string, MixedStyleDeclaration> = {};
  allTags.forEach(tag => {
    merged[tag] = {...(base[tag] ?? {}), ...overrides[tag]};
  });
  return merged;
};

/**
 * `react-native-render-html` defaults markers to fontSize 14 / lineHeight
 * 14 * 1.3, so they sit on a different baseline than 16 / 24 body text.
 * Derive marker metrics from the merged styles of the closest text-flow
 * wrapper around list items so caller overrides propagate to `1.`, `A.`,
 * `•` markers. Editors like TipTap / TinyMCE emit `<li><p>…</p></li>`, so
 * `p` is the actual line carrier; fall back to `li` for raw `<li>` HTML.
 */
export const getListMarkerTextStyle = (
  tagsStyles: Record<string, MixedStyleDeclaration>,
  defaultBodyTextColor: TextStyle['color'],
): TextStyle => {
  const pStyle = tagsStyles.p ?? {};
  const liStyle = tagsStyles.li ?? {};
  const pickStyle = <K extends keyof MixedStyleDeclaration>(
    key: K,
  ): MixedStyleDeclaration[K] | undefined => pStyle[key] ?? liStyle[key];

  const fontSize = Number(pickStyle('fontSize'));

  return {
    fontSize: !isNaN(fontSize) ? fontSize : DEFAULT_BODY_FONT_SIZE,
    lineHeight: pickStyle('lineHeight') ?? LINE_HEIGHT,
    color: pickStyle('color') ?? defaultBodyTextColor,
    fontFamily: pickStyle('fontFamily'),
    fontWeight: pickStyle('fontWeight'),
    fontStyle: pickStyle('fontStyle'),
  };
};
