import { theme } from 'antdv-next';
import { computed, reactive, watch } from 'vue';

import {
  PREVIOUS_BLOG_AUTHOR_AVATAR,
  PREVIOUS_BLOG_BACKGROUND_IMAGE,
  LOCAL_BLOG_AUTHOR_AVATAR,
  LOCAL_BLOG_BACKGROUND_IMAGE,
  resolveBlogStaticAsset,
  unwrapBlogCssImage,
} from '@/data/blogStaticAssets';
import { createBlogMotionCssVariables } from '@/factories/blogAnimationFactory';
import {
  buildBlogAdminSsoUrl,
  isLegacyBlogManagementHref,
} from '@/factories/blogAdminSsoFactory';
import { BLOG_META_NAMES, blogDomId, blogDomSelector, blogMetaSelector } from '@/factories/blogDomFactory';

export type BlogThemeMode = 'dark' | 'light';
export type BlogFontMode = 'sans' | 'serif';
export type BlogShadowMode = 'small' | 'big';
export type BlogFilterMode = 'off' | 'sunset' | 'darkness' | 'grayscale';

export interface BlogThemePreferences {
  colorPrimary: string;
  filter: BlogFilterMode;
  font: BlogFontMode;
  mode: BlogThemeMode;
  radius: number;
  shadow: BlogShadowMode;
}

export interface BlogThemeMenuItem {
  external?: boolean;
  href: string;
  icon?: string;
  label: string;
}

export interface WordpressArgonThemeConfig {
  argonConfig?: {
    codeHighlight?: {
      breakLine?: boolean;
      enable?: boolean;
      hideLinenumber?: boolean;
      transparentLinenumber?: boolean;
    };
    dateFormat?: string;
    disablePjax?: boolean;
    foldLongComments?: boolean;
    foldLongShuoshuo?: boolean;
    headroom?: boolean | string;
    language?: string;
    lazyload?: {
      effect?: string;
      threshold?: number;
    };
    pangu?: string;
    pjaxAnimationDuration?: number;
    waterflowColumns?: number | string;
    wpPath?: string;
    zoomify?: boolean;
  };
  backgroundDarkBrightness?: number | string;
  backgroundDarkImage?: string;
  backgroundDarkOpacity?: number | string;
  backgroundImage?: string;
  backgroundOpacity?: number | string;
  bodyClass?: string | string[];
  darkmodeAutoSwitch?: 'alwaysoff' | 'alwayson' | 'system' | 'time' | string;
  enableCustomThemeColor?: boolean;
  headerMenu?: BlogThemeMenuItem[];
  headerMenuVisible?: boolean | string;
  htmlClass?: string | string[];
  site?: {
    authorAvatar?: string;
    authorName?: string;
    description?: string;
    home?: string;
    title?: string;
    url?: string;
  };
  sidebarMenu?: BlogThemeMenuItem[];
  themeCardRadius?: number | string;
  themeColor?: string;
  themeColorRgb?: string;
  themeVersion?: string;
  [key: string]: unknown;
}

interface BlogRuntimeThemeConfig {
  articleHeaderStyle: 'default' | string;
  backgroundDarkBrightness: number;
  backgroundDarkImage: string;
  backgroundDarkOpacity: number;
  backgroundImage: string;
  backgroundOpacity: number;
  bodyClass: string[];
  headerMenu: BlogThemeMenuItem[];
  headerMenuVisible: boolean;
  siteAuthorAvatar: string;
  htmlClass: string[];
  immersionColor: boolean;
  siteAuthorName: string;
  siteDescription: string;
  siteHome: string;
  siteTitle: string;
  siteUrl: string;
  sidebarMenu: BlogThemeMenuItem[];
  themeVersion: string;
  toolbarBlur: boolean;
  tripleColumn: boolean;
  wordpressThemeConfig: WordpressArgonThemeConfig | null;
}

const STORAGE_KEY = 'KT_BLOG_THEME_PREFERENCES';
const BLOG_THEME_BLOCK_CLASS = 'kt-blog';
const ARGON_SANS_FONT_FAMILY =
  'Comfortaa, "Open Sans", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif';
const ARGON_SERIF_FONT_FAMILY = 'Georgia, "Times New Roman", "Noto Serif SC", serif';
const ARGON_DEFAULT_COLOR_PRIMARY = '#c3a1ed';
const ARGON_PRIMARY_SOFT = '#4a4058';
const ARGON_CARD_SHADOW = '0 2px 4px rgba(0, 0, 0, 0.075)';
const ARGON_DEFAULT_BACKGROUND = PREVIOUS_BLOG_BACKGROUND_IMAGE;
const ARGON_DEFAULT_AUTHOR_AVATAR = PREVIOUS_BLOG_AUTHOR_AVATAR;
const defaultHeaderMenu: BlogThemeMenuItem[] = [];
const defaultSidebarMenu: BlogThemeMenuItem[] = [
  { href: '/', icon: 'fa-home', label: '首页' },
  { external: true, href: buildBlogAdminSsoUrl(), icon: 'fa-user', label: '管理' },
];

const defaultPreferences: BlogThemePreferences = {
  colorPrimary: ARGON_DEFAULT_COLOR_PRIMARY,
  filter: 'off',
  font: 'sans',
  mode: 'dark',
  radius: 4,
  shadow: 'small',
};

const preferences = reactive<BlogThemePreferences>(loadPreferences());
const runtimeConfig = reactive<BlogRuntimeThemeConfig>({
  articleHeaderStyle: 'default',
  backgroundDarkBrightness: 0.65,
  backgroundDarkImage: ARGON_DEFAULT_BACKGROUND,
  backgroundDarkOpacity: 1,
  backgroundImage: ARGON_DEFAULT_BACKGROUND,
  backgroundOpacity: 1,
  bodyClass: ['home', 'blog', 'wp-theme-argon'],
  headerMenu: [...defaultHeaderMenu],
  headerMenuVisible: false,
  htmlClass: [
    'triple-column',
    'immersion-color',
    'toolbar-blur',
    'article-header-style-default',
  ],
  immersionColor: true,
  siteAuthorAvatar: ARGON_DEFAULT_AUTHOR_AVATAR,
  siteAuthorName: 'KwiTsukasa',
  siteDescription: '',
  siteHome: '',
  siteTitle: 'KwiTsukasa的小站',
  siteUrl: '',
  sidebarMenu: [...defaultSidebarMenu],
  themeVersion: '',
  toolbarBlur: true,
  tripleColumn: true,
  wordpressThemeConfig: null,
});
let themeWatcherReady = false;
let systemThemeMediaQuery: MediaQueryList | null = null;
let systemThemeChangeHandler:
  | ((event: MediaQueryList | MediaQueryListEvent) => void)
  | null = null;

const isDarkTheme = computed(() => preferences.mode === 'dark');
const siteConfig = computed(() => ({
  authorAvatar: runtimeConfig.siteAuthorAvatar,
  authorName: runtimeConfig.siteAuthorName,
  description: runtimeConfig.siteDescription,
  headerMenu: runtimeConfig.headerMenu,
  headerMenuVisible: runtimeConfig.headerMenuVisible,
  home: runtimeConfig.siteHome,
  sidebarMenu: runtimeConfig.sidebarMenu,
  title: runtimeConfig.siteTitle,
  url: runtimeConfig.siteUrl,
}));
const wordpressThemeConfig = computed(() => runtimeConfig.wordpressThemeConfig);
const themeRootClass = computed(() => [
  BLOG_THEME_BLOCK_CLASS,
  `${BLOG_THEME_BLOCK_CLASS}--wp-argon`,
  `${BLOG_THEME_BLOCK_CLASS}--home`,
  `${BLOG_THEME_BLOCK_CLASS}--blog`,
  runtimeConfig.tripleColumn && `${BLOG_THEME_BLOCK_CLASS}--triple-column`,
  runtimeConfig.immersionColor && `${BLOG_THEME_BLOCK_CLASS}--immersion-color`,
  runtimeConfig.toolbarBlur && `${BLOG_THEME_BLOCK_CLASS}--toolbar-blur`,
  `${BLOG_THEME_BLOCK_CLASS}--article-header-${runtimeConfig.articleHeaderStyle}`,
  runtimeConfig.themeVersion && `${BLOG_THEME_BLOCK_CLASS}--argon-${runtimeConfig.themeVersion.replace(/\./g, '-')}`,
  `${BLOG_THEME_BLOCK_CLASS}--${preferences.mode}`,
  preferences.font === 'serif' && `${BLOG_THEME_BLOCK_CLASS}--font-serif`,
  preferences.shadow === 'big' && `${BLOG_THEME_BLOCK_CLASS}--shadow-big`,
  isThemeColorTooDark(preferences.colorPrimary) && `${BLOG_THEME_BLOCK_CLASS}--theme-too-dark`,
].filter(Boolean).join(' '));

const themeConfig = computed(() => {
  const palette = createThemePalette(preferences.colorPrimary, preferences.mode);

  return {
    algorithm: isDarkTheme.value ? theme.darkAlgorithm : theme.defaultAlgorithm,
    components: {
      Button: {
        borderRadius: preferences.radius,
        colorPrimary: preferences.colorPrimary,
        controlHeightLG: 42,
      },
      Checkbox: {
        colorPrimary: preferences.colorPrimary,
      },
      Input: {
        activeBorderColor: preferences.colorPrimary,
        hoverBorderColor: preferences.colorPrimary,
      },
      Modal: {
        borderRadiusLG: preferences.radius,
        colorBgElevated: palette.card,
      },
      Switch: {
        colorPrimary: preferences.colorPrimary,
      },
    },
    token: {
      borderRadius: preferences.radius,
      colorBgBase: palette.page,
      colorBgContainer: palette.card,
      colorBorder: palette.border,
      colorPrimary: preferences.colorPrimary,
      colorText: palette.text,
      colorTextSecondary: palette.muted,
      fontFamily: getThemeFontFamily(preferences.font),
    },
  };
});

/**
 * @param nextMode 目标主题模式，和 Argon 的 darkmode/lightmode 类保持一致。
 */
function setThemeMode(nextMode: BlogThemeMode) {
  preferences.mode = nextMode;
}

/**
 * @param nextFont 目标字体模式，sans 对应 Argon 默认无衬线，serif 对应 use-serif。
 */
function setFontMode(nextFont: BlogFontMode) {
  preferences.font = nextFont;
}

/**
 * @param nextShadow 阴影强度，big 会启用 Argon 的 use-big-shadow 类。
 */
function setShadowMode(nextShadow: BlogShadowMode) {
  preferences.shadow = nextShadow;
}

/**
 * @param nextFilter Argon 视觉滤镜名称，off 会移除全部滤镜类。
 */
function setFilterMode(nextFilter: BlogFilterMode) {
  preferences.filter = nextFilter;
}

/**
 * @param nextRadius 卡片圆角像素值，同步到 CSS 变量和 antdv-next token。
 */
function setRadius(nextRadius: number) {
  preferences.radius = nextRadius;
}

/**
 * @param nextColor 主题色十六进制值，同步 Argon toolbar 和 antdv-next 组件色。
 */
function setPrimaryColor(nextColor: string) {
  preferences.colorPrimary = nextColor;
}

function applyWordpressThemeConfig(config: WordpressArgonThemeConfig) {
  const nextHtmlClass = normalizeClassList(config.htmlClass);
  const nextBodyClass = normalizeClassList(config.bodyClass);
  const nextBackgroundDarkBrightness = normalizePositiveNumber(config.backgroundDarkBrightness);
  const nextBackgroundDarkOpacity = normalizeOpacity(config.backgroundDarkOpacity);
  const nextBackgroundOpacity = normalizeOpacity(config.backgroundOpacity);
  const nextColor = normalizeHexColor(config.themeColor);
  const nextRadius = normalizeRadius(config.themeCardRadius);
  const hasRemoteHtmlClass = nextHtmlClass.length > 0;

  runtimeConfig.wordpressThemeConfig = config;

  runtimeConfig.htmlClass = nextHtmlClass.length ? nextHtmlClass : runtimeConfig.htmlClass;
  runtimeConfig.bodyClass = nextBodyClass.length ? nextBodyClass : runtimeConfig.bodyClass;
  if (Array.isArray(config.headerMenu)) {
    runtimeConfig.headerMenu = normalizeMenuItems(
      config.headerMenu,
      config.site?.home || config.site?.url || runtimeConfig.siteHome || runtimeConfig.siteUrl,
    );
  }
  runtimeConfig.headerMenuVisible = normalizeHeaderMenuVisible(config);
  runtimeConfig.tripleColumn = hasRemoteHtmlClass
    ? nextHtmlClass.includes('triple-column')
    : runtimeConfig.tripleColumn;
  runtimeConfig.immersionColor = hasRemoteHtmlClass
    ? nextHtmlClass.includes('immersion-color')
    : runtimeConfig.immersionColor;
  runtimeConfig.toolbarBlur = hasRemoteHtmlClass
    ? nextHtmlClass.includes('toolbar-blur')
    : runtimeConfig.toolbarBlur;
  runtimeConfig.articleHeaderStyle =
    getArticleHeaderStyle(nextHtmlClass) ||
    (hasRemoteHtmlClass ? 'default' : runtimeConfig.articleHeaderStyle);
  runtimeConfig.backgroundDarkBrightness =
    typeof nextBackgroundDarkBrightness === 'number'
      ? nextBackgroundDarkBrightness
      : runtimeConfig.backgroundDarkBrightness;
  runtimeConfig.backgroundDarkImage =
    normalizeCssImage(config.backgroundDarkImage) || runtimeConfig.backgroundDarkImage;
  runtimeConfig.backgroundDarkOpacity =
    typeof nextBackgroundDarkOpacity === 'number'
      ? nextBackgroundDarkOpacity
      : runtimeConfig.backgroundDarkOpacity;
  runtimeConfig.backgroundImage = normalizeCssImage(config.backgroundImage) || runtimeConfig.backgroundImage;
  runtimeConfig.backgroundOpacity =
    typeof nextBackgroundOpacity === 'number'
      ? nextBackgroundOpacity
      : runtimeConfig.backgroundOpacity;
  runtimeConfig.siteAuthorAvatar =
    normalizeThemeAsset(config.site?.authorAvatar) || runtimeConfig.siteAuthorAvatar;
  runtimeConfig.siteDescription = config.site?.description ?? runtimeConfig.siteDescription;
  runtimeConfig.siteAuthorName = config.site?.authorName || runtimeConfig.siteAuthorName;
  runtimeConfig.siteHome = config.site?.home || runtimeConfig.siteHome;
  runtimeConfig.siteTitle = config.site?.title || runtimeConfig.siteTitle;
  runtimeConfig.siteUrl = config.site?.url || runtimeConfig.siteUrl;
  if (Array.isArray(config.sidebarMenu)) {
    runtimeConfig.sidebarMenu = normalizeSidebarMenuItems(
      config.sidebarMenu,
      config.site?.home || config.site?.url || runtimeConfig.siteHome || runtimeConfig.siteUrl,
    );
  }
  runtimeConfig.themeVersion = config.themeVersion || runtimeConfig.themeVersion;

  if (nextColor && config.enableCustomThemeColor !== false) {
    preferences.colorPrimary = nextColor;
  }

  if (hasRemoteHtmlClass) {
    preferences.font = nextHtmlClass.includes('use-serif') ? 'serif' : 'sans';
    preferences.shadow = nextHtmlClass.includes('use-big-shadow') ? 'big' : 'small';
  }

  if (typeof nextRadius === 'number') {
    preferences.radius = nextRadius;
  }

  syncSystemThemeMode(config);
  const nextMode = getModeFromWordpressConfig(config);
  if (nextMode) {
    preferences.mode = nextMode;
  }
}

/**
 * @returns 当前 Blog Web 的主题偏好、Antdv token 配置与偏好更新函数。
 */
export function useBlogTheme() {
  ensureThemeWatcher();

  return {
    isDarkTheme,
    preferences,
    applyWordpressThemeConfig,
    siteConfig,
    setFilterMode,
    setFontMode,
    setPrimaryColor,
    setRadius,
    setShadowMode,
    setThemeMode,
    themeConfig,
    themeRootClass,
    wordpressThemeConfig,
  };
}

/**
 * @returns localStorage 中的主题偏好；解析失败时回退到默认 Argon 深色方案。
 */
function loadPreferences(): BlogThemePreferences {
  if (typeof window === 'undefined') {
    return { ...defaultPreferences };
  }

  try {
    const rawValue = window.localStorage.getItem(STORAGE_KEY);
    if (!rawValue) {
      return { ...defaultPreferences };
    }

    return {
      ...defaultPreferences,
      ...JSON.parse(rawValue),
    };
  } catch {
    return { ...defaultPreferences };
  }
}

/**
 * 注册一次全局主题监听，复用 kt-template-admin 的“偏好 -> CSS 变量 -> Antdv token”链路。
 */
function ensureThemeWatcher() {
  if (themeWatcherReady) {
    return;
  }

  themeWatcherReady = true;
  watch(
    () => ({
      preferences: { ...preferences },
      runtimeConfig: { ...runtimeConfig },
    }),
    ({ preferences: currentPreferences }) => {
      if (typeof document === 'undefined') {
        return;
      }

      applyCssVariables(currentPreferences);
      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(currentPreferences));
    },
    {
      deep: true,
      immediate: true,
    },
  );
}

/**
 * @param currentPreferences 当前主题偏好，用于生成命名空间内的 CSS 变量。
 */
function applyCssVariables(currentPreferences: BlogThemePreferences) {
  const primaryRgb = hexToRgb(currentPreferences.colorPrimary);
  const palette = createThemePalette(currentPreferences.colorPrimary, currentPreferences.mode);
  const fontFamily = getThemeFontFamily(currentPreferences.font);
  const argonColorVariables = createArgonColorVariables(primaryRgb, currentPreferences.mode);
  const backgroundImage = appendCssImageFallback(
    normalizeCssImage(
      currentPreferences.mode === 'dark'
        ? runtimeConfig.backgroundDarkImage || runtimeConfig.backgroundImage
        : runtimeConfig.backgroundImage,
    ) || `url('${ARGON_DEFAULT_BACKGROUND}')`,
    LOCAL_BLOG_BACKGROUND_IMAGE,
  );
  const backgroundOpacity =
    currentPreferences.mode === 'dark'
      ? runtimeConfig.backgroundDarkOpacity
      : runtimeConfig.backgroundOpacity;

  const primaryHsl = rgbToHsl(primaryRgb.r, primaryRgb.g, primaryRgb.b);
  const rootThemeVariables = `
  --themecolor: ${currentPreferences.colorPrimary};
  --themecolor-R: ${primaryRgb.r};
  --themecolor-G: ${primaryRgb.g};
  --themecolor-B: ${primaryRgb.b};
  --themecolor-H: ${primaryHsl.h};
  --themecolor-S: ${primaryHsl.s};
  --themecolor-L: ${primaryHsl.l};
  --themecolor-dark0: ${formatArgonThemeHsl(primaryHsl, -2.5)};
  --themecolor-dark: ${formatArgonThemeHsl(primaryHsl, -5)};
  --themecolor-dark2: ${formatArgonThemeHsl(primaryHsl, -10)};
  --themecolor-dark3: ${formatArgonThemeHsl(primaryHsl, -15)};
  --themecolor-light: ${formatArgonThemeHsl(primaryHsl, 10)};
  --themecolor-gradient: linear-gradient(150deg, var(--themecolor-light) 15%, var(--themecolor) 70%, var(--themecolor-dark0) 94%);
  --themecolor-rgbstr: ${primaryRgb.r}, ${primaryRgb.g}, ${primaryRgb.b};
  --argon-page: var(--color-background);
  --argon-card: var(--color-foreground);
  --argon-card-deep: var(--color-foreground);
  --argon-card-soft: var(--color-widgets);
  --argon-card-overlay-weak: ${palette.cardOverlayWeak};
  --argon-card-overlay-strong: ${palette.cardOverlayStrong};
  --argon-control: var(--color-border-on-foreground-deeper);
  --argon-control-soft: var(--color-widgets);
  --argon-pill: var(--color-widgets-disabled);
  --argon-text: ${palette.text};
  --argon-muted: ${palette.muted};
  --argon-title: ${palette.title};
  --argon-border: var(--color-border);
  --argon-meta: ${palette.meta};
  --argon-widget-text: ${palette.widgetText};
  --argon-subtle: ${palette.subtle};
  --argon-faint: ${palette.faint};
  --argon-placeholder: ${palette.placeholder};
  --argon-scrollbar-track: var(--kt-blog-scrollbar-track);
  --argon-scrollbar-thumb: var(--kt-blog-scrollbar-thumb);
  --argon-scrollbar-thumb-hover: var(--kt-blog-scrollbar-thumb-hover);
  --argon-scrollbar-thin-thumb: ${palette.scrollbarThinThumb};
  --argon-scrollbar-size: var(--kt-blog-scrollbar-size);
`;

  ensureThemeMetaElement(BLOG_META_NAMES.themeColor).setAttribute('content', currentPreferences.colorPrimary);
  ensureThemeMetaElement(BLOG_META_NAMES.themeColorRgb).setAttribute('content', `${primaryRgb.r}, ${primaryRgb.g}, ${primaryRgb.b}`);

  updateThemeStyle(`
:root {
  --kt-blog-scrollbar-track: ${palette.scrollbarTrack};
  --kt-blog-scrollbar-thumb: ${palette.scrollbarThumb};
  --kt-blog-scrollbar-thumb-hover: ${palette.scrollbarThumbHover};
  --kt-blog-scrollbar-size: 10px;
${rootThemeVariables}
${serializeCssVariables(argonColorVariables.root)}
}

body.wp-theme-argon {
${serializeCssVariables(argonColorVariables.current)}
}

.${BLOG_THEME_BLOCK_CLASS} {
  --radius: ${currentPreferences.radius}px;
  --card-radius: ${currentPreferences.radius}px;
  --argon-background-image: ${backgroundImage};
  --argon-background-light-image: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.backgroundImage) || `url('${ARGON_DEFAULT_BACKGROUND}')`, LOCAL_BLOG_BACKGROUND_IMAGE)};
  --argon-background-dark-image: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.backgroundDarkImage) || backgroundImage, LOCAL_BLOG_BACKGROUND_IMAGE)};
  --argon-background-filter: ${currentPreferences.mode === 'dark' ? `brightness(${runtimeConfig.backgroundDarkBrightness})` : 'none'};
  --argon-background-opacity: ${backgroundOpacity};
  --argon-background-light-opacity: ${runtimeConfig.backgroundOpacity};
  --argon-background-dark-opacity: ${runtimeConfig.backgroundDarkOpacity};
  --argon-author-avatar: ${appendCssImageFallback(normalizeCssImage(runtimeConfig.siteAuthorAvatar) || `url('${ARGON_DEFAULT_AUTHOR_AVATAR}')`, LOCAL_BLOG_AUTHOR_AVATAR)};
  --argon-font-family: ${fontFamily};
  --argon-shadow: ${ARGON_CARD_SHADOW};
  --argon-primary-soft: ${ARGON_PRIMARY_SOFT};
${createBlogMotionCssVariables()}
${rootThemeVariables}
}
`);
}

/**
 * @param font 当前字体模式。
 * @returns Argon 主题使用的完整字体栈。
 */
function getThemeFontFamily(font: BlogFontMode) {
  return font === 'serif' ? ARGON_SERIF_FONT_FAMILY : ARGON_SANS_FONT_FAMILY;
}

/**
 * @param cssText 主题动态变量，文档滚动条使用 :root 变量，业务样式仍收敛在 .kt-blog。
 */
function updateThemeStyle(cssText: string) {
  let styleElement = document.querySelector<HTMLStyleElement>(blogDomSelector('themeStyle'));
  if (!styleElement) {
    styleElement = document.createElement('style');
    styleElement.id = blogDomId('themeStyle');
    document.head.appendChild(styleElement);
  }

  styleElement.textContent = cssText.trim();
}

/**
 * @param colorPrimary 当前主题色。
 * @param mode 明暗模式。
 * @returns Argon 容器、标题、弱文本和边框使用的派生色板。
 */
function createThemePalette(colorPrimary: string, mode: BlogThemeMode) {
  const { b, g, r } = hexToRgb(colorPrimary);
  const { h, s } = rgbToHsl(r, g, b);

  if (mode === 'light') {
    const paleSaturation = Math.min(Math.max(Math.round(s * 0.38), 16), 42);

    return {
      border: 'rgba(15, 23, 42, 0.1)',
      card: `hsl(${h}, ${paleSaturation}%, 98%)`,
      cardDeep: `hsl(${h}, ${paleSaturation}%, 96%)`,
      cardOverlayStrong: 'rgba(15, 23, 42, 0.56)',
      cardOverlayWeak: 'rgba(15, 23, 42, 0.08)',
      cardSoft: `hsl(${h}, ${paleSaturation}%, 94%)`,
      control: `hsl(${h}, ${Math.min(paleSaturation + 8, 52)}%, 88%)`,
      controlSoft: `hsl(${h}, ${Math.min(paleSaturation + 6, 50)}%, 92%)`,
      faint: 'rgba(23, 32, 51, 0.28)',
      meta: 'rgba(23, 32, 51, 0.56)',
      muted: '#5b6472',
      page: '#f7f8fb',
      pill: `hsl(${h}, ${Math.min(paleSaturation + 10, 56)}%, 90%)`,
      placeholder: 'rgba(23, 32, 51, 0.42)',
      scrollbarThinThumb: 'rgba(0, 0, 0, 0.2)',
      scrollbarThumb: 'rgba(0, 0, 0, 0.25)',
      scrollbarThumbHover: `rgba(${r}, ${g}, ${b}, 0.7)`,
      scrollbarTrack: 'transparent',
      subtle: 'rgba(23, 32, 51, 0.48)',
      text: '#172033',
      title: '#263146',
      toolbarRgb: '111, 95, 137',
      widgetText: 'rgba(23, 32, 51, 0.66)',
    };
  }

  const baseSaturation = Math.min(Math.max(Math.round(s * 0.22), 8), 24);
  const textSaturation = Math.min(Math.max(Math.round(s * 0.42), 24), 70);
  const mutedSaturation = Math.min(Math.max(Math.round(s * 0.16), 12), 32);

  return {
    border: 'rgba(255, 255, 255, 0.06)',
    card: '#2f2b33',
    cardDeep: '#2f2b33',
    cardOverlayStrong: `hsla(${h}, ${baseSaturation + 6}%, 16%, 0.72)`,
    cardOverlayWeak: `rgba(${r}, ${g}, ${b}, 0.1)`,
    cardSoft: '#2f2b33',
    control: `hsl(${h}, ${baseSaturation + 12}%, 28%)`,
    controlSoft: `hsl(${h}, ${baseSaturation + 10}%, 24%)`,
    faint: 'rgba(238, 238, 238, 0.34)',
    meta: '#eeeeee',
    muted: `hsl(${h}, ${mutedSaturation}%, 72%)`,
    page: `hsl(${h}, ${baseSaturation}%, 14%)`,
    pill: `hsl(${h}, ${baseSaturation + 12}%, 26%)`,
    placeholder: 'rgba(238, 238, 238, 0.45)',
    scrollbarThinThumb: 'rgba(255, 255, 255, 0.2)',
    scrollbarThumb: 'rgba(255, 255, 255, 0.25)',
    scrollbarThumbHover: `rgba(${r}, ${g}, ${b}, 0.7)`,
    scrollbarTrack: 'transparent',
    subtle: 'rgba(238, 238, 238, 0.6)',
    text: '#eeeeee',
    title: '#deccf5',
    toolbarRgb: `${r}, ${g}, ${b}`,
    widgetText: 'rgba(238, 238, 238, 0.66)',
  };
}

/**
 * @param hexColor 目标颜色十六进制字符串。
 * @returns RGB 三通道数值。
 */
function hexToRgb(hexColor: string) {
  const normalized = hexColor.replace('#', '');
  const value =
    normalized.length === 3
      ? normalized
          .split('')
          .map((item) => item + item)
          .join('')
      : normalized.padEnd(6, '0').slice(0, 6);

  return {
    b: Number.parseInt(value.slice(4, 6), 16),
    g: Number.parseInt(value.slice(2, 4), 16),
    r: Number.parseInt(value.slice(0, 2), 16),
  };
}

/**
 * @param name Meta 标签名称，Argon 用它同步浏览器主题色。
 * @returns 现有或新建的 meta 元素，保证后续主题应用不会因为缺标签而静默失效。
 */
function ensureThemeMetaElement(name: string) {
  let metaElement = document.querySelector<HTMLMetaElement>(blogMetaSelector(name));
  if (!metaElement) {
    metaElement = document.createElement('meta');
    metaElement.name = name;
    document.head.appendChild(metaElement);
  }

  return metaElement;
}

/**
 * @param r 红色通道。
 * @param g 绿色通道。
 * @param b 蓝色通道。
 * @returns HSL 色相、饱和度和亮度，用于复现 Argon 原生主题变量。
 */
function rgbToHsl(r: number, g: number, b: number) {
  const red = r / 255;
  const green = g / 255;
  const blue = b / 255;
  const max = Math.max(red, green, blue);
  const min = Math.min(red, green, blue);
  const lightness = (max + min) / 2;

  if (max === min) {
    return { h: 0, l: Math.round(lightness * 100), s: 0 };
  }

  const delta = max - min;
  const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
  const hue =
    max === red
      ? (green - blue) / delta + (green < blue ? 6 : 0)
      : max === green
        ? (blue - red) / delta + 2
        : (red - green) / delta + 4;

  return {
    h: Math.round(hue * 60),
    l: Math.round(lightness * 100),
    s: Math.round(saturation * 100),
  };
}

/**
 * @param primaryRgb 当前主题色 RGB，Argon 用它派生 immersion-color 明暗色阶。
 * @param mode 当前明暗模式，决定 body 上暴露亮色 tint 变量还是暗色 shade 变量。
 * @returns root 默认变量和 body 当前变量，供 CSS 继承链模拟 WordPress Argon。
 */
function createArgonColorVariables(
  primaryRgb: ReturnType<typeof hexToRgb>,
  mode: BlogThemeMode,
) {
  const root = {
    '--color-background': '#f4f5f7',
    '--color-border': '#dce0e5',
    '--color-border-on-foreground': '#f3f3f3',
    '--color-border-on-foreground-deeper': '#eee',
    '--color-darkmode-banner': '',
    '--color-darkmode-toolbar': '',
    '--color-foreground': '#fff',
    '--color-shade-70': formatArgonShadeChannels(primaryRgb, 0.7),
    '--color-shade-75': formatArgonShadeChannels(primaryRgb, 0.75),
    '--color-shade-80': formatArgonShadeChannels(primaryRgb, 0.8),
    '--color-shade-82': formatArgonShadeChannels(primaryRgb, 0.82),
    '--color-shade-86': formatArgonShadeChannels(primaryRgb, 0.86),
    '--color-shade-90': formatArgonShadeChannels(primaryRgb, 0.9),
    '--color-shade-94': formatArgonShadeChannels(primaryRgb, 0.94),
    '--color-shade-96': formatArgonShadeChannels(primaryRgb, 0.96),
    '--color-text-deeper': '#212529',
    '--color-tint-82': formatArgonTintChannels(primaryRgb, 0.82),
    '--color-widgets': '#fff',
    '--color-widgets-disabled': '#e9ecef',
  };

  const light = {
    '--color-background': formatArgonTintRgb(primaryRgb, 0.86),
    '--color-border': formatArgonTintRgb(primaryRgb, 0.78),
    '--color-border-on-foreground': formatArgonTintRgb(primaryRgb, 0.86),
    '--color-border-on-foreground-deeper': formatArgonTintRgb(primaryRgb, 0.8),
    '--color-darkmode-banner': '',
    '--color-darkmode-toolbar': '',
    '--color-foreground': formatArgonTintRgb(primaryRgb, 0.92),
    '--color-text-deeper': formatArgonShadeRgb(primaryRgb, 0.82),
    '--color-widgets': formatArgonTintRgb(primaryRgb, 0.95),
    '--color-widgets-disabled': formatArgonTintRgb(primaryRgb, 0.86),
  };
  const dark = {
    '--color-background': formatArgonShadeRgb(primaryRgb, 0.94),
    '--color-border': formatArgonShadeRgb(primaryRgb, 0.8),
    '--color-border-on-foreground': formatArgonShadeRgb(primaryRgb, 0.82),
    '--color-border-on-foreground-deeper': formatArgonShadeRgb(primaryRgb, 0.75),
    '--color-darkmode-banner': formatArgonShadeRgb(primaryRgb, 0.96),
    '--color-darkmode-toolbar': formatArgonShadeChannels(primaryRgb, 0.9),
    '--color-foreground': formatArgonShadeRgb(primaryRgb, 0.9),
    '--color-text-deeper': formatArgonTintRgb(primaryRgb, 0.82),
    '--color-widgets': formatArgonShadeRgb(primaryRgb, 0.86),
    '--color-widgets-disabled': formatArgonShadeRgb(primaryRgb, 0.82),
  };

  return {
    current: mode === 'dark' ? dark : light,
    root,
  };
}

/**
 * @param variables CSS custom property map generated for the current Argon theme.
 * @returns Multiline CSS declarations ready to inject into the singleton theme style.
 */
function serializeCssVariables(variables: Record<string, string>) {
  return Object.entries(variables)
    .map(([name, value]) => `  ${name}: ${value};`)
    .join('\n');
}

/**
 * @param hsl 当前主题色 HSL，来源于 `--themecolor`。
 * @param lightnessDelta Argon 原生亮度偏移；正数生成 light，负数生成 dark 阶梯。
 * @returns 复刻 WordPress Argon `--themecolor-light/dark*` 的 HSL calc 表达式。
 */
function formatArgonThemeHsl(
  hsl: ReturnType<typeof rgbToHsl>,
  lightnessDelta: number,
) {
  const direction = lightnessDelta >= 0 ? '+' : '-';
  const boundary = lightnessDelta >= 0 ? '100%' : '0%';
  const clampFunction = lightnessDelta >= 0 ? 'min' : 'max';
  const delta = Math.abs(lightnessDelta);

  return `hsl(${hsl.h}, calc(${hsl.s} * 1%), ${clampFunction}(calc(${hsl.l} * 1% ${direction} ${delta}%), ${boundary}))`;
}

/**
 * @param primaryRgb 当前主题色 RGB。
 * @param ratio Argon shade 系数，越接近 1 越贴近深色基准 30。
 * @returns 可作为 `rgb()` 通道列表使用的 shade calc 表达式。
 */
function formatArgonShadeChannels(primaryRgb: ReturnType<typeof hexToRgb>, ratio: number) {
  return [
    formatArgonShadeChannel(primaryRgb.r, ratio),
    formatArgonShadeChannel(primaryRgb.g, ratio),
    formatArgonShadeChannel(primaryRgb.b, ratio),
  ].join(',\n    ');
}

/**
 * @param primaryRgb 当前主题色 RGB。
 * @param ratio Argon tint 系数，越接近 1 越贴近白色基准 255。
 * @returns 可作为 `rgb()` 通道列表使用的 tint calc 表达式。
 */
function formatArgonTintChannels(primaryRgb: ReturnType<typeof hexToRgb>, ratio: number) {
  return [
    formatArgonTintChannel(primaryRgb.r, ratio),
    formatArgonTintChannel(primaryRgb.g, ratio),
    formatArgonTintChannel(primaryRgb.b, ratio),
  ].join(',\n    ');
}

/**
 * @param primaryRgb 当前主题色 RGB。
 * @param ratio Argon shade 系数。
 * @returns 完整 `rgb(...)` shade 表达式。
 */
function formatArgonShadeRgb(primaryRgb: ReturnType<typeof hexToRgb>, ratio: number) {
  return `rgb(${formatArgonShadeChannels(primaryRgb, ratio)})`;
}

/**
 * @param primaryRgb 当前主题色 RGB。
 * @param ratio Argon tint 系数。
 * @returns 完整 `rgb(...)` tint 表达式。
 */
function formatArgonTintRgb(primaryRgb: ReturnType<typeof hexToRgb>, ratio: number) {
  return `rgb(${formatArgonTintChannels(primaryRgb, ratio)})`;
}

/**
 * @param channel 当前主题色单通道值。
 * @param ratio Argon shade 系数。
 * @returns 单个深色通道 calc 表达式，基准值 30 来自 Argon immersion-color 算法。
 */
function formatArgonShadeChannel(channel: number, ratio: number) {
  return `calc(30 * ${ratio} + ${channel} * (1 - ${ratio}))`;
}

/**
 * @param channel 当前主题色单通道值。
 * @param ratio Argon tint 系数。
 * @returns 单个浅色通道 calc 表达式，基准值 255 来自 Argon immersion-color 算法。
 */
function formatArgonTintChannel(channel: number, ratio: number) {
  return `calc(${channel} + (255 - ${channel}) * ${ratio})`;
}

/**
 * @param r 红色通道。
 * @param g 绿色通道。
 * @param b 蓝色通道。
 * @returns Argon 用于判断主题色是否过暗的灰度值。
 */
function getGray(r: number, g: number, b: number) {
  return r * 0.299 + g * 0.587 + b * 0.114;
}

/**
 * @param hexColor 当前主题色。
 * @returns 主题色是否过暗，用于根节点 modifier。
 */
function isThemeColorTooDark(hexColor: string) {
  const { b, g, r } = hexToRgb(hexColor);

  return getGray(r, g, b) < 50;
}

function normalizeClassList(value?: string | string[]): string[] {
  if (Array.isArray(value)) {
    return value.flatMap((item) => normalizeClassList(item));
  }

  return String(value || '')
    .split(/\s+/)
    .map((item) => item.trim())
    .filter(Boolean);
}

function normalizeHexColor(value?: string) {
  if (!value) return '';
  const normalized = value.trim();

  if (/^#[0-9a-f]{6}$/i.test(normalized)) return normalized.toUpperCase();
  if (/^#[0-9a-f]{3}$/i.test(normalized)) {
    return `#${normalized
      .slice(1)
      .split('')
      .map((item) => item + item)
      .join('')}`.toUpperCase();
  }

  return '';
}

function normalizeRadius(value?: number | string) {
  if (value === undefined || value === null || value === '') return null;
  const numericValue = Number(value);

  return Number.isFinite(numericValue) ? Math.max(0, numericValue) : null;
}

function normalizeOpacity(value?: number | string) {
  if (value === undefined || value === null || value === '') return null;
  const numericValue = Number(value);

  return Number.isFinite(numericValue)
    ? Math.min(Math.max(numericValue, 0), 1)
    : null;
}

function normalizePositiveNumber(value?: number | string) {
  if (value === undefined || value === null || value === '') return null;
  const numericValue = Number(value);

  return Number.isFinite(numericValue) ? Math.max(0, numericValue) : null;
}

/**
 * @param config WordPress/Argon 主题配置，`headerMenu` 只表示菜单数据，不表示 toolbar 菜单位置已启用。
 * @returns 是否渲染顶部工具栏菜单；未提供显式开关时默认关闭，复现当前线上 WordPress 未启用顶部菜单位置的行为。
 */
function normalizeHeaderMenuVisible(config: WordpressArgonThemeConfig) {
  const rawValue = config.headerMenuVisible ?? config.toolbarMenuVisible ?? config.showHeaderMenu ?? config.showToolbarMenu;

  return normalizeBooleanFlag(rawValue) === true;
}

/**
 * @param value 后端或内联配置给出的布尔语义值，兼容 WordPress 字符串选项和真实 boolean。
 * @returns 可识别的布尔值；无法识别时返回 undefined，避免把菜单数据误当成显隐开关。
 */
function normalizeBooleanFlag(value: unknown): boolean | undefined {
  if (typeof value === 'boolean') {
    return value;
  }

  if (typeof value !== 'string') {
    return undefined;
  }

  const normalized = value.trim().toLowerCase();
  if (['1', 'true', 'yes', 'on', 'show', 'visible', 'enabled'].includes(normalized)) {
    return true;
  }

  if (['0', 'false', 'no', 'off', 'hide', 'hidden', 'disabled'].includes(normalized)) {
    return false;
  }

  return undefined;
}

/**
 * @param value Theme image URL returned by the API or static defaults.
 * @returns CSS `url(...)` token after replacing legacy Argon demo placeholders with previous blog assets.
 */
function normalizeCssImage(value?: string) {
  const normalized = resolveBlogStaticAsset(value, '');

  if (normalized.startsWith('url(')) return normalized;
  if (/^https?:\/\//i.test(normalized) || normalized.startsWith('/')) {
    return `url('${normalized.replace(/'/g, "\\'")}')`;
  }

  return '';
}

/**
 * @param primaryCssImage CSS `url(...)` token for the preferred remote image.
 * @param localFallback Local packaged image path used when the remote image cannot load.
 * @returns A CSS background-image layer list with the remote image first and local backup second.
 */
function appendCssImageFallback(primaryCssImage: string, localFallback: string) {
  const fallbackCssImage = normalizeCssImage(localFallback);

  if (!fallbackCssImage || primaryCssImage.includes(fallbackCssImage)) {
    return primaryCssImage;
  }

  return `${primaryCssImage}, ${fallbackCssImage}`;
}

/**
 * @param value Theme asset URL returned by WordPress-compatible config.
 * @returns Plain URL/path after replacing legacy Argon demo placeholders, suitable for component state.
 */
function normalizeThemeAsset(value?: string) {
  const asset = resolveBlogStaticAsset(unwrapBlogCssImage(value), '');

  return /^https?:\/\//i.test(asset) || asset.startsWith('/') ? asset : '';
}

function normalizeMenuItems(items: BlogThemeMenuItem[], siteHome = ''): BlogThemeMenuItem[] {
  return items.reduce<BlogThemeMenuItem[]>((result, item) => {
    const label = `${item.label || ''}`.trim();
    const href = normalizeMenuHref(`${item.href || ''}`.trim(), siteHome);

    if (!label || !href) return result;

    const isInternalHref = href.startsWith('/') && !href.startsWith('//');
    result.push({
      external: !isInternalHref && (item.external || /^https?:\/\//i.test(href)),
      href,
      ...(item.icon ? { icon: item.icon } : {}),
      label,
    });

    return result;
  }, []);
}

/**
 * Normalizes sidebar items and migrates historical management entries to KT Admin SSO.
 * @param items Raw sidebar items supplied by the Blog theme API.
 * @param siteHome Public Blog home URL used to collapse same-site links into RouterLink paths.
 * @returns Sidebar items with only historical management destinations replaced by an external SSO URL.
 */
function normalizeSidebarMenuItems(
  items: BlogThemeMenuItem[],
  siteHome = '',
): BlogThemeMenuItem[] {
  return normalizeMenuItems(items, siteHome).map((item) =>
    isLegacyBlogManagementHref(item.href)
      ? {
          ...item,
          external: true,
          href: buildBlogAdminSsoUrl(),
        }
      : item,
  );
}

function normalizeMenuHref(href: string, siteHome = '') {
  if (!href) return '';
  const normalizedSiteHome = siteHome.replace(/\/+$/g, '');

  if (normalizedSiteHome && href.replace(/\/+$/g, '') === normalizedSiteHome) {
    return '/';
  }

  if (normalizedSiteHome && href.startsWith(`${normalizedSiteHome}/`)) {
    return href.slice(normalizedSiteHome.length) || '/';
  }

  return href;
}

function getModeFromWordpressConfig(config: WordpressArgonThemeConfig): BlogThemeMode | '' {
  const htmlClass = normalizeClassList(config.htmlClass);
  const switchMode = `${config.darkmodeAutoSwitch || ''}`.toLowerCase();

  if (htmlClass.includes('darkmode')) return 'dark';
  if (switchMode === 'alwayson') return 'dark';
  if (switchMode === 'alwaysoff' || switchMode === 'false') return 'light';
  if (switchMode === 'system') return getSystemThemeMode();
  if (switchMode === 'time') return getTimeThemeMode();

  return '';
}

function getSystemThemeMode(): BlogThemeMode | '' {
  if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
    return '';
  }

  return window.matchMedia('(prefers-color-scheme: dark)').matches
    ? 'dark'
    : 'light';
}

function syncSystemThemeMode(config: WordpressArgonThemeConfig) {
  stopSystemThemeModeSync();
  if (
    `${config.darkmodeAutoSwitch || ''}`.toLowerCase() !== 'system' ||
    typeof window === 'undefined' ||
    typeof window.matchMedia !== 'function'
  ) {
    return;
  }

  systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
  systemThemeChangeHandler = (event) => {
    preferences.mode = event.matches ? 'dark' : 'light';
  };

  if (typeof systemThemeMediaQuery.addEventListener === 'function') {
    systemThemeMediaQuery.addEventListener('change', systemThemeChangeHandler);
  } else {
    systemThemeMediaQuery.addListener(systemThemeChangeHandler);
  }
}

function stopSystemThemeModeSync() {
  if (!systemThemeMediaQuery || !systemThemeChangeHandler) {
    return;
  }

  if (typeof systemThemeMediaQuery.removeEventListener === 'function') {
    systemThemeMediaQuery.removeEventListener(
      'change',
      systemThemeChangeHandler,
    );
  } else {
    systemThemeMediaQuery.removeListener(systemThemeChangeHandler);
  }

  systemThemeMediaQuery = null;
  systemThemeChangeHandler = null;
}

function getTimeThemeMode(): BlogThemeMode {
  const hour = new Date().getHours();

  return hour < 7 || hour >= 22 ? 'dark' : 'light';
}

function getArticleHeaderStyle(htmlClass: string[]) {
  const prefix = 'article-header-style-';
  const className = htmlClass.find((item) => item.startsWith(prefix));

  return className?.slice(prefix.length) || '';
}
