<script lang="ts" setup>
import { computed, nextTick, reactive, ref, watch } from 'vue';

import { Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';

import {
  Button,
  Card,
  Input,
  InputNumber,
  message,
  Select,
  Space,
  Spin,
  Switch,
  Table,
  TabPane,
  Tabs,
  Tag,
  TextArea,
} from 'antdv-next';

import {
  getVoiceRoomRocketConfig,
  getVoiceRoomRocketLevelConfigs,
  getVoiceRoomRocketRewardConfigs,
  saveVoiceRoomRocketConfig,
  saveVoiceRoomRocketLevelConfigs,
  saveVoiceRoomRocketRewardConfigs,
} from '#/api/legacy/resident-activity';
import { getAllowedSysOrigins } from '#/views/system/shared';

import ResourceSelectDrawer from './components/resource-select-drawer.vue';

defineOptions({ name: 'ResidentVoiceRoomRocketPage' });

const RIYADH_TIMEZONE = 'Asia/Riyadh';
const DEFAULT_MAX_LEVEL = 6;
const RULE_TEXT_LOCALES = [
  { label: 'English', value: 'en' },
  { label: '中文', value: 'zh-CN' },
];
const RULE_TEXT_FONT_SIZE_OPTIONS = [
  { label: '12px', value: '12px' },
  { label: '14px', value: '14px' },
  { label: '16px', value: '16px' },
  { label: '18px', value: '18px' },
  { label: '20px', value: '20px' },
  { label: '24px', value: '24px' },
  { label: '28px', value: '28px' },
  { label: '32px', value: '32px' },
];
const RULE_TEXT_COLOR_OPTIONS = [
  { label: '默认黑', value: '#111827' },
  { label: '灰色', value: '#6b7280' },
  { label: '红色', value: '#ef4444' },
  { label: '橙色', value: '#f97316' },
  { label: '黄色', value: '#eab308' },
  { label: '绿色', value: '#22c55e' },
  { label: '蓝色', value: '#3b82f6' },
  { label: '紫色', value: '#8b5cf6' },
];

type LevelRow = {
  enabled: boolean;
  id?: number | string;
  level: number;
  needEnergy: number;
  previewRocketUrl: string;
  rocketAnimationUrl: string;
  rocketIconUrl: string;
  shakeThresholdPercent: string;
  sort: number;
};

type RewardRow = {
  enabled: boolean;
  expireDays?: null | number;
  id?: number | string;
  level: number;
  localKey: string;
  rewardAmount: number;
  rewardCopies: number;
  rewardCover: string;
  rewardItemId?: null | number | string;
  rewardName: string;
  rewardScene: string;
  rewardType: string;
  sort: number;
  weight: number;
};

type ResourceOption = {
  amount?: number | string;
  cover: string;
  id: number | string;
  name: string;
  remark?: string;
  sourceUrl: string;
  type: string;
};

type LevelResourceField =
  | 'previewRocketUrl'
  | 'rocketAnimationUrl'
  | 'rocketIconUrl';

const accessStore = useAccessStore();
const sysOriginOptions = computed(() => {
  const options = getAllowedSysOrigins(accessStore.accessCodes || []);
  return options.length > 0 ? options : getAllowedSysOrigins([]);
});

const selectedSysOrigin = ref('');
const activeTab = ref('config');
const rewardLevel = ref(1);

const configLoading = ref(false);
const levelLoading = ref(false);
const rewardLoading = ref(false);
const configSaving = ref(false);
const levelSaving = ref(false);
const rewardSaving = ref(false);

const configForm = reactive({
  broadcastDurationSeconds: 7,
  configured: false,
  enabled: false,
  energyRuleJsonText: '{}',
  id: '',
  inRoomRewardDelaySeconds: 10,
  maxLevel: DEFAULT_MAX_LEVEL,
  rankingTopLimit: 100,
  rewardRecordKeepDays: 35,
  ruleTextMap: Object.fromEntries(
    RULE_TEXT_LOCALES.map((item) => [item.value, '']),
  ) as Record<string, string>,
  timezone: RIYADH_TIMEZONE,
  updateTime: '',
});

const levelRows = ref<LevelRow[]>([]);
const rewardRows = ref<RewardRow[]>([]);
const ruleTextLocale = ref(RULE_TEXT_LOCALES[0]?.value || 'zh-CN');
const ruleTextExtra = ref<Record<string, unknown>>({});
const ruleEditorRef = ref<HTMLElement | null>(null);
const ruleTextColor = ref(RULE_TEXT_COLOR_OPTIONS[0]?.value || '#111827');
const ruleTextFontSize = ref(RULE_TEXT_FONT_SIZE_OPTIONS[2]?.value || '16px');
const ruleTextSavedRange = ref<null | Range>(null);

const resourcePickerOpen = ref(false);
const resourcePickerTitle = ref('选择资源');
const resourcePickerDefaultType = ref('ALL_PROPS');
const resourcePickerFixedType = ref(false);
const resourcePickerTarget = ref<
  | null
  | {
      field: LevelResourceField;
      kind: 'level';
      row: Record<string, any>;
    }
  | {
      kind: 'reward';
      row: Record<string, any>;
    }
>(null);

const sceneOptions = [
  { label: '贡献第一', value: 'TOP1' },
  { label: '点火人', value: 'IGNITE' },
  { label: '在房用户', value: 'IN_ROOM' },
];

const rewardTypeOptions = [
  { label: '金币', value: 'GOLD' },
  { label: '坐骑', value: 'MOUNT' },
  { label: '头像框', value: 'AVATAR_FRAME' },
  { label: '气泡', value: 'CHAT_BUBBLE' },
  { label: 'VIP', value: 'VIP_CARD' },
  { label: '礼物', value: 'GIFT' },
  { label: '徽章', value: 'BADGE' },
  { label: '轮空', value: 'NONE' },
];

const levelOptions = computed(() =>
  Array.from({ length: configForm.maxLevel || DEFAULT_MAX_LEVEL }, (_, index) => ({
    label: `Lv.${index + 1}`,
    value: index + 1,
  })),
);

function rewardTypeOptionsForRow(row: Record<string, any>) {
  return row.rewardScene === 'IN_ROOM'
    ? rewardTypeOptions
    : rewardTypeOptions.filter((item) => item.value !== 'NONE');
}

const statusMeta = computed(() => {
  if (!configForm.configured) {
    return { color: 'warning', text: '未配置' };
  }
  return configForm.enabled
    ? { color: 'success', text: '已启用' }
    : { color: 'default', text: '已关闭' };
});

const levelColumns = [
  { dataIndex: 'level', key: 'level', title: '等级', width: 90 },
  { dataIndex: 'needEnergy', key: 'needEnergy', title: '所需能量', width: 160 },
  {
    dataIndex: 'previewRocketUrl',
    key: 'previewRocketUrl',
    title: '预览火箭',
    width: 260,
  },
  { dataIndex: 'rocketIconUrl', key: 'rocketIconUrl', title: '火箭图标', width: 260 },
  {
    dataIndex: 'rocketAnimationUrl',
    key: 'rocketAnimationUrl',
    title: '发射动画',
    width: 260,
  },
  { dataIndex: 'shakeThresholdPercent', key: 'shakeThresholdPercent', title: '晃动阈值(%)', width: 160 },
  { dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
  { dataIndex: 'sort', key: 'sort', title: '排序', width: 100 },
];

const rewardColumns = [
  { dataIndex: 'rewardScene', key: 'rewardScene', title: '奖励场景', width: 150 },
  { dataIndex: 'rewardType', key: 'rewardType', title: '奖励类型', width: 150 },
  { dataIndex: 'rewardItemId', key: 'rewardItemId', title: '资源', width: 220 },
  { dataIndex: 'rewardName', key: 'rewardName', title: '奖励名称', width: 180 },
  { dataIndex: 'rewardCover', key: 'rewardCover', title: '封面', width: 220 },
  { dataIndex: 'rewardAmount', key: 'rewardAmount', title: '数量/金币/天数', width: 150 },
  { dataIndex: 'expireDays', key: 'expireDays', title: '有效天数', width: 130 },
  { dataIndex: 'rewardCopies', key: 'rewardCopies', title: '份数', width: 110 },
  { dataIndex: 'weight', key: 'weight', title: '权重', width: 110 },
  { dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
  { dataIndex: 'sort', key: 'sort', title: '排序', width: 100 },
  { dataIndex: 'actions', key: 'actions', title: '操作', width: 100 },
];

function createDefaultLevel(level: number): LevelRow {
  return {
    enabled: true,
    level,
    needEnergy: level * 100,
    previewRocketUrl: '',
    rocketAnimationUrl: '',
    rocketIconUrl: '',
    shakeThresholdPercent: '95.0000',
    sort: level,
  };
}

function stringifyJSON(value: unknown) {
  if (value === null || value === undefined || value === '') {
    return '{}';
  }
  if (typeof value === 'string') {
    return value.trim() || '{}';
  }
  return JSON.stringify(value, null, 2);
}

function trimEditorHTML(value: string) {
  const html = String(value || '').trim();
  return html === '<br>' || html === '<div><br></div>' ? '' : html;
}

function extractRuleTextHTML(value: unknown) {
  if (typeof value === 'string') {
    return value;
  }
  if (value && typeof value === 'object' && !Array.isArray(value)) {
    const record = value as Record<string, unknown>;
    const html = record.html ?? record.content ?? record.value;
    return typeof html === 'string' ? html : JSON.stringify(value, null, 2);
  }
  return '';
}

function renderRuleTextEditor() {
  void nextTick(() => {
    const editor = ruleEditorRef.value;
    if (!editor) {
      return;
    }
    const html = configForm.ruleTextMap[ruleTextLocale.value] || '';
    if (editor.innerHTML !== html) {
      editor.innerHTML = html;
    }
  });
}

function normalizeRuleText(value: unknown) {
  const nextMap = Object.fromEntries(
    RULE_TEXT_LOCALES.map((item) => [item.value, '']),
  ) as Record<string, string>;
  const extra: Record<string, unknown> = {};

  if (typeof value === 'string') {
    nextMap['zh-CN'] = value;
  } else if (value && typeof value === 'object' && !Array.isArray(value)) {
    const localeSet = new Set(RULE_TEXT_LOCALES.map((item) => item.value));
    Object.entries(value as Record<string, unknown>).forEach(([key, item]) => {
      if (localeSet.has(key)) {
        nextMap[key] = extractRuleTextHTML(item);
      } else {
        extra[key] = item;
      }
    });
  }

  configForm.ruleTextMap = nextMap;
  ruleTextExtra.value = extra;
  renderRuleTextEditor();
}

function sanitizeRuleTextHTML(value: string) {
  const html = trimEditorHTML(value);
  if (!html || typeof document === 'undefined') {
    return html;
  }

  const container = document.createElement('div');
  container.innerHTML = html;
  container
    .querySelectorAll('script, style, iframe, object, embed, link, meta')
    .forEach((node) => node.remove());
  container.querySelectorAll('*').forEach((node) => {
    [...node.attributes].forEach((attr) => {
      const name = attr.name.toLowerCase();
      const attributeValue = attr.value.trim().toLowerCase();
      if (name.startsWith('on') || attributeValue.startsWith('javascript:')) {
        node.removeAttribute(attr.name);
      }
    });
  });
  return trimEditorHTML(container.innerHTML);
}

function syncRuleTextFromEditor(locale = ruleTextLocale.value) {
  const editor = ruleEditorRef.value;
  if (!editor || !locale) {
    return;
  }
  configForm.ruleTextMap[locale] = sanitizeRuleTextHTML(editor.innerHTML);
}

function saveRuleTextSelection() {
  if (typeof window === 'undefined') {
    return;
  }
  const editor = ruleEditorRef.value;
  const selection = window.getSelection();
  if (!editor || !selection || selection.rangeCount === 0) {
    return;
  }
  const range = selection.getRangeAt(0);
  if (editor.contains(range.commonAncestorContainer)) {
    ruleTextSavedRange.value = range.cloneRange();
  }
}

function restoreRuleTextSelection() {
  const editor = ruleEditorRef.value;
  if (!editor || typeof window === 'undefined') {
    return;
  }
  editor.focus();
  const selection = window.getSelection();
  const range = ruleTextSavedRange.value;
  if (!selection || !range || !editor.contains(range.commonAncestorContainer)) {
    return;
  }
  selection.removeAllRanges();
  selection.addRange(range);
}

function normalizeGeneratedFontTags(fontSize?: string) {
  const editor = ruleEditorRef.value;
  if (!editor) {
    return;
  }
  editor.querySelectorAll('font').forEach((node) => {
    const span = document.createElement('span');
    const color = node.getAttribute('color');
    span.innerHTML = node.innerHTML;
    if (color) {
      span.style.color = color;
    }
    if (fontSize && node.getAttribute('size')) {
      span.style.fontSize = fontSize;
    }
    node.replaceWith(span);
  });
}

function applyRuleTextCommand(command: string, value?: string) {
  restoreRuleTextSelection();
  document.execCommand(command, false, value);
  normalizeGeneratedFontTags();
  syncRuleTextFromEditor();
  saveRuleTextSelection();
}

function clearRuleTextFormat() {
  applyRuleTextCommand('removeFormat');
}

function applyRuleTextFontSize(value: string) {
  ruleTextFontSize.value = value;
  restoreRuleTextSelection();
  document.execCommand('fontSize', false, '7');
  normalizeGeneratedFontTags(value);
  syncRuleTextFromEditor();
  saveRuleTextSelection();
}

function applyRuleTextColor(value: string) {
  ruleTextColor.value = value;
  restoreRuleTextSelection();
  document.execCommand('foreColor', false, value);
  normalizeGeneratedFontTags();
  syncRuleTextFromEditor();
  saveRuleTextSelection();
}

function handleRuleTextFontSizeChange(value: string) {
  applyRuleTextFontSize(value);
}

function handleRuleTextColorInput(event: Event) {
  const target = event.target as HTMLInputElement | null;
  if (target?.value) {
    applyRuleTextColor(target.value);
  }
}

function handleRuleTextPaste() {
  window.setTimeout(() => syncRuleTextFromEditor());
}

function buildRuleTextPayload() {
  syncRuleTextFromEditor();
  const payload: Record<string, unknown> = { ...ruleTextExtra.value };
  RULE_TEXT_LOCALES.forEach((item) => {
    const html = sanitizeRuleTextHTML(configForm.ruleTextMap[item.value] || '');
    if (html) {
      payload[item.value] = html;
    } else {
      delete payload[item.value];
    }
  });
  return payload;
}

function parseJSONText(value: string, label: string) {
  const raw = String(value || '').trim();
  if (!raw) {
    return {};
  }
  try {
    return JSON.parse(raw);
  } catch {
    message.warning(`${label} 必须是合法 JSON`);
    return undefined;
  }
}

function toNumber(value: unknown, fallback = 0) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : fallback;
}

function toOptionalNumber(value: unknown) {
  if (value === null || value === undefined || value === '') {
    return undefined;
  }
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : undefined;
}

function toOptionalString(value: unknown) {
  const text = String(value ?? '').trim();
  return text || undefined;
}

function normalizeConfig(value: null | Record<string, any> | undefined) {
  const next = value || {};
  configForm.broadcastDurationSeconds = toNumber(
    next.broadcastDurationSeconds,
    7,
  );
  configForm.configured = Boolean(next.configured);
  configForm.enabled = Boolean(next.enabled);
  configForm.energyRuleJsonText = stringifyJSON(next.energyRuleJson);
  configForm.id = String(next.id || '');
  configForm.inRoomRewardDelaySeconds = toNumber(
    next.inRoomRewardDelaySeconds,
    10,
  );
  configForm.maxLevel = toNumber(next.maxLevel, DEFAULT_MAX_LEVEL);
  configForm.rankingTopLimit = toNumber(next.rankingTopLimit, 100);
  configForm.rewardRecordKeepDays = toNumber(next.rewardRecordKeepDays, 35);
  normalizeRuleText(next.ruleText);
  configForm.timezone = RIYADH_TIMEZONE;
  configForm.updateTime = String(next.updateTime || '');
}

function normalizeLevelRows(rows: Array<Record<string, any>> | undefined) {
  const source = Array.isArray(rows) ? rows : [];
  const byLevel = new Map<number, Record<string, any>>();
  source.forEach((item) => {
    byLevel.set(toNumber(item.level), item);
  });
  levelRows.value = Array.from({ length: DEFAULT_MAX_LEVEL }, (_, index) => {
    const level = index + 1;
    const row = byLevel.get(level) || {};
    return {
      ...createDefaultLevel(level),
      enabled: row.enabled === undefined ? true : Boolean(row.enabled),
      id: row.id,
      needEnergy: toNumber(row.needEnergy, level * 100),
      previewRocketUrl: String(row.previewRocketUrl || ''),
      rocketAnimationUrl: String(row.rocketAnimationUrl || ''),
      rocketIconUrl: String(row.rocketIconUrl || ''),
      shakeThresholdPercent: String(row.shakeThresholdPercent || '95.0000'),
      sort: toNumber(row.sort, level),
    };
  });
}

function normalizeRewardRows(rows: Array<Record<string, any>> | undefined) {
  rewardRows.value = (Array.isArray(rows) ? rows : []).map((row, index) => ({
    enabled: row.enabled === undefined ? true : Boolean(row.enabled),
    expireDays: toOptionalNumber(row.expireDays) ?? null,
    id: row.id,
    level: toNumber(row.level, rewardLevel.value),
    localKey: `${row.id || 'new'}-${index}-${Date.now()}`,
    rewardAmount: toNumber(row.rewardAmount, 1),
    rewardCover: String(row.rewardCover || ''),
    rewardItemId: row.rewardItemId ?? null,
    rewardName: String(row.rewardName || ''),
    rewardScene: String(row.rewardScene || 'TOP1'),
    rewardType: String(row.rewardType || 'GOLD'),
    rewardCopies: toNumber(row.rewardCopies, 1),
    sort: toNumber(row.sort, index + 1),
    weight: toNumber(row.weight, 1),
  }));
}

function rewardResourceType(rewardType: string) {
  const value = String(rewardType || '').trim();
  const mapping: Record<string, string> = {
    AVATAR_FRAME: 'AVATAR_FRAME',
    BADGE: 'BADGE',
    CHAT_BUBBLE: 'CHAT_BUBBLE',
    GIFT: 'GIFT',
    MOUNT: 'RIDE',
    VIP_CARD: 'NOBLE_VIP',
  };
  return mapping[value] || '';
}

function rewardTypeText(rewardType: string) {
  return (
    rewardTypeOptions.find((item) => item.value === rewardType)?.label ||
    rewardType ||
    '资源'
  );
}

function isRewardResourceDisabled(row: Record<string, any>) {
  return row.rewardType === 'GOLD' || row.rewardType === 'NONE';
}

function openLevelResourcePicker(
  row: Record<string, any>,
  field: LevelResourceField,
) {
  resourcePickerTarget.value = {
    field,
    kind: 'level',
    row,
  };
  const titleMap: Record<LevelResourceField, string> = {
    previewRocketUrl: '选择预览火箭资源',
    rocketAnimationUrl: '选择发射动画资源',
    rocketIconUrl: '选择火箭图标资源',
  };
  resourcePickerTitle.value = titleMap[field];
  resourcePickerDefaultType.value = 'ALL_PROPS';
  resourcePickerFixedType.value = false;
  resourcePickerOpen.value = true;
}

function openRewardResourcePicker(row: Record<string, any>) {
  if (isRewardResourceDisabled(row)) {
    return;
  }
  const sourceType = rewardResourceType(row.rewardType);
  if (!sourceType) {
    return;
  }
  resourcePickerTarget.value = {
    kind: 'reward',
    row,
  };
  resourcePickerTitle.value = `选择${rewardTypeText(row.rewardType)}资源`;
  resourcePickerDefaultType.value = sourceType;
  resourcePickerFixedType.value = true;
  resourcePickerOpen.value = true;
}

function clearLevelResource(
  row: Record<string, any>,
  field: LevelResourceField,
) {
  row[field] = '';
}

function clearRewardResource(row: Record<string, any>) {
  row.rewardItemId = null;
  row.rewardCover = '';
  if (row.rewardName !== '金币' && row.rewardName !== '轮空') {
    row.rewardName = '';
  }
}

function handleRewardSceneChange(row: Record<string, any>) {
  if (row.rewardScene !== 'IN_ROOM' && row.rewardType === 'NONE') {
    row.rewardType = 'GOLD';
    handleRewardTypeChange(row);
  }
  row.rewardCopies = row.rewardScene === 'IN_ROOM' ? toNumber(row.rewardCopies, 1) : 1;
}

function handleResourceSelect(item: ResourceOption) {
  const target = resourcePickerTarget.value;
  if (!target) {
    return;
  }
  if (target.kind === 'level') {
    const value =
      target.field === 'rocketAnimationUrl'
        ? String(item.sourceUrl || item.cover || '')
        : String(item.cover || item.sourceUrl || '');
    target.row[target.field] = value;
  } else {
    target.row.rewardItemId = item.id;
    target.row.rewardName = item.name || String(item.id || '');
    target.row.rewardCover = item.cover || item.sourceUrl || '';
  }
  resourcePickerOpen.value = false;
  resourcePickerTarget.value = null;
}

function resourceUrlName(url: string) {
  const text = String(url || '').trim();
  if (!text) {
    return '未选择';
  }
  return text;
}

async function loadConfig() {
  if (!selectedSysOrigin.value) {
    return;
  }
  configLoading.value = true;
  try {
    const result = await getVoiceRoomRocketConfig(selectedSysOrigin.value);
    normalizeConfig(result);
  } finally {
    configLoading.value = false;
  }
}

async function loadLevels() {
  if (!selectedSysOrigin.value) {
    return;
  }
  levelLoading.value = true;
  try {
    const result = await getVoiceRoomRocketLevelConfigs(selectedSysOrigin.value);
    normalizeLevelRows(result);
  } finally {
    levelLoading.value = false;
  }
}

async function loadRewards() {
  if (!selectedSysOrigin.value) {
    return;
  }
  rewardLoading.value = true;
  try {
    const result = await getVoiceRoomRocketRewardConfigs({
      level: rewardLevel.value,
      sysOrigin: selectedSysOrigin.value,
    });
    normalizeRewardRows(result);
  } finally {
    rewardLoading.value = false;
  }
}

async function loadAll() {
  await Promise.all([loadConfig(), loadLevels(), loadRewards()]);
}

async function saveConfig() {
  const energyRuleJson = parseJSONText(
    configForm.energyRuleJsonText,
    '能量规则',
  );
  if (energyRuleJson === undefined) {
    return;
  }
  const ruleText = buildRuleTextPayload();
  configSaving.value = true;
  try {
    const result = await saveVoiceRoomRocketConfig({
      broadcastDurationSeconds: configForm.broadcastDurationSeconds,
      enabled: configForm.enabled,
      energyRuleJson,
      inRoomRewardDelaySeconds: configForm.inRoomRewardDelaySeconds,
      maxLevel: DEFAULT_MAX_LEVEL,
      rankingTopLimit: configForm.rankingTopLimit,
      rewardRecordKeepDays: configForm.rewardRecordKeepDays,
      ruleText,
      sysOrigin: selectedSysOrigin.value,
      timezone: RIYADH_TIMEZONE,
    });
    normalizeConfig(result);
    message.success('保存成功');
  } finally {
    configSaving.value = false;
  }
}

async function saveLevels() {
  levelSaving.value = true;
  try {
    const result = await saveVoiceRoomRocketLevelConfigs({
      levels: levelRows.value.map((row) => ({
        enabled: row.enabled,
        id: row.id,
        level: row.level,
        needEnergy: toNumber(row.needEnergy, row.level * 100),
        previewRocketUrl: row.previewRocketUrl,
        rocketAnimationUrl: row.rocketAnimationUrl,
        rocketIconUrl: row.rocketIconUrl,
        shakeThresholdPercent: row.shakeThresholdPercent,
        sort: toNumber(row.sort, row.level),
      })),
      sysOrigin: selectedSysOrigin.value,
    });
    normalizeLevelRows(result);
    message.success('保存成功');
  } finally {
    levelSaving.value = false;
  }
}

function addRewardRow(scene = 'TOP1', rewardType = 'GOLD') {
  const isNone = rewardType === 'NONE';
  rewardRows.value.push({
    enabled: true,
    expireDays: null,
    level: rewardLevel.value,
    localKey: `new-${Date.now()}-${rewardRows.value.length}`,
    rewardAmount: isNone ? 0 : 1,
    rewardCover: '',
    rewardItemId: null,
    rewardName: isNone ? '轮空' : (rewardType === 'GOLD' ? '金币' : ''),
    rewardScene: scene,
    rewardType,
    rewardCopies: 1,
    sort: rewardRows.value.length + 1,
    weight: 1,
  });
}

function removeRewardRow(index: number) {
  rewardRows.value.splice(index, 1);
}

function handleRewardTypeChange(row: Record<string, any>) {
  if (row.rewardType === 'NONE') {
    if (row.rewardScene !== 'IN_ROOM') {
      row.rewardScene = 'IN_ROOM';
    }
    row.rewardItemId = null;
    row.rewardCover = '';
    row.expireDays = null;
    row.rewardAmount = 0;
    row.rewardName = '轮空';
    return;
  }
  if (row.rewardType === 'GOLD') {
    row.rewardItemId = null;
    row.rewardCover = '';
    row.expireDays = null;
    if (!row.rewardName) {
      row.rewardName = '金币';
    }
    return;
  }
  row.rewardItemId = null;
  row.rewardCover = '';
  if (row.rewardName === '金币' || row.rewardName === '轮空') {
    row.rewardName = '';
  }
}

function validateRewards() {
  for (const row of rewardRows.value) {
    if (!row.rewardScene) {
      message.warning('请选择奖励场景');
      return false;
    }
    if (!row.rewardType) {
      message.warning('请选择奖励类型');
      return false;
    }
    if (row.rewardType === 'NONE' && row.rewardScene !== 'IN_ROOM') {
      message.warning('轮空只能配置在房用户奖励');
      return false;
    }
    if (row.rewardScene !== 'IN_ROOM') {
      row.rewardCopies = 1;
    } else if (toNumber(row.rewardCopies, 0) <= 0) {
      message.warning('在房用户奖励份数必须大于 0');
      return false;
    }
    if (!String(row.rewardName || '').trim()) {
      message.warning('请输入奖励名称');
      return false;
    }
    if (
      row.rewardType !== 'GOLD' &&
      row.rewardType !== 'NONE' &&
      !String(row.rewardItemId || '').trim()
    ) {
      message.warning('非金币奖励必须选择资源');
      return false;
    }
  }
  return true;
}

async function saveRewards() {
  if (!validateRewards()) {
    return;
  }
  rewardSaving.value = true;
  try {
    const result = await saveVoiceRoomRocketRewardConfigs({
      level: rewardLevel.value,
      rewards: rewardRows.value.map((row) => ({
        enabled: row.enabled,
        expireDays:
          row.rewardType === 'GOLD' || row.rewardType === 'NONE'
            ? undefined
            : toOptionalNumber(row.expireDays),
        id: row.id,
        level: rewardLevel.value,
        rewardAmount:
          row.rewardType === 'NONE' ? 0 : toNumber(row.rewardAmount, 1),
        rewardCover: row.rewardType === 'NONE' ? '' : row.rewardCover,
        rewardItemId:
          row.rewardType === 'GOLD' || row.rewardType === 'NONE'
            ? undefined
            : toOptionalString(row.rewardItemId),
        rewardName: row.rewardType === 'NONE' ? '轮空' : row.rewardName.trim(),
        rewardScene: row.rewardScene,
        rewardType: row.rewardType,
        rewardCopies:
          row.rewardScene === 'IN_ROOM' ? toNumber(row.rewardCopies, 1) : 1,
        sort: toNumber(row.sort, 1),
        weight: toNumber(row.weight, 1),
      })),
      sysOrigin: selectedSysOrigin.value,
    });
    normalizeRewardRows(result);
    message.success('保存成功');
  } finally {
    rewardSaving.value = false;
  }
}

watch(
  sysOriginOptions,
  (options) => {
    if (!selectedSysOrigin.value && options[0]?.value) {
      selectedSysOrigin.value = String(options[0].value);
    }
  },
  { immediate: true },
);

watch(
  selectedSysOrigin,
  () => {
    void loadAll();
  },
  { immediate: true },
);

watch(rewardLevel, () => {
  void loadRewards();
});

watch(ruleTextLocale, (_next, previous) => {
  syncRuleTextFromEditor(previous);
  renderRuleTextEditor();
});
</script>

<template>
  <Page title="火箭">
    <div class="voice-room-rocket-page">
      <Card>
        <Space wrap>
          <span>系统</span>
          <Select
            v-model:value="selectedSysOrigin"
            :options="sysOriginOptions"
            style="width: 180px"
          />
          <Tag :color="statusMeta.color">{{ statusMeta.text }}</Tag>
          <Tag color="processing">利雅得时间每日 00:00 重置</Tag>
          <span v-if="configForm.updateTime" class="muted-text">
            更新时间：{{ configForm.updateTime }}
          </span>
        </Space>
      </Card>

      <Tabs v-model:active-key="activeTab">
        <TabPane key="config" tab="基础配置">
          <Spin :spinning="configLoading">
            <Card>
              <div class="config-grid">
                <label class="field-row">
                  <span>启用火箭</span>
                  <Switch v-model:checked="configForm.enabled" />
                </label>
                <label class="field-row">
                  <span>活动时区</span>
                  <Input
                    v-model:value="configForm.timezone"
                    class="field-control"
                    disabled
                  />
                </label>
                <label class="field-row">
                  <span>最大等级</span>
                  <InputNumber
                    v-model:value="configForm.maxLevel"
                    :min="1"
                    :max="6"
                    :precision="0"
                    class="field-control"
                    disabled
                  />
                </label>
                <label class="field-row">
                  <span>榜单人数</span>
                  <InputNumber
                    v-model:value="configForm.rankingTopLimit"
                    :min="1"
                    :max="100"
                    :precision="0"
                    class="field-control"
                  />
                </label>
                <label class="field-row">
                  <span>发奖记录保留天数</span>
                  <InputNumber
                    v-model:value="configForm.rewardRecordKeepDays"
                    :min="1"
                    :precision="0"
                    class="field-control"
                  />
                </label>
                <label class="field-row">
                  <span>发射广播秒数</span>
                  <InputNumber
                    v-model:value="configForm.broadcastDurationSeconds"
                    :min="1"
                    :precision="0"
                    class="field-control"
                  />
                </label>
                <label class="field-row">
                  <span>在房奖励延迟秒数</span>
                  <InputNumber
                    v-model:value="configForm.inRoomRewardDelaySeconds"
                    :min="1"
                    :precision="0"
                    class="field-control"
                  />
                </label>
                <label class="field-row field-wide">
                  <span>能量规则 JSON</span>
                  <TextArea
                    v-model:value="configForm.energyRuleJsonText"
                    :rows="4"
                    class="field-control"
                  />
                </label>
                <div class="field-row field-wide">
                  <span>规则说明富文本</span>
                  <div class="rule-editor-panel">
                    <div class="rule-editor-toolbar" @mousedown="saveRuleTextSelection">
                      <Space wrap>
                        <Select
                          v-model:value="ruleTextLocale"
                          :options="RULE_TEXT_LOCALES"
                          class="rule-editor-locale"
                        />
                        <Select
                          v-model:value="ruleTextFontSize"
                          :options="RULE_TEXT_FONT_SIZE_OPTIONS"
                          class="rule-editor-size"
                          @change="handleRuleTextFontSizeChange"
                        />
                        <div class="rule-editor-colors">
                          <button
                            v-for="item in RULE_TEXT_COLOR_OPTIONS"
                            :key="item.value"
                            :aria-label="item.label"
                            :class="{
                              'rule-color-swatch--active': ruleTextColor === item.value,
                            }"
                            :style="{ backgroundColor: item.value }"
                            class="rule-color-swatch"
                            type="button"
                            @mousedown.prevent="applyRuleTextColor(item.value)"
                          ></button>
                          <input
                            :value="ruleTextColor"
                            aria-label="自定义字体颜色"
                            class="rule-color-picker"
                            type="color"
                            @input="handleRuleTextColorInput"
                            @mousedown="saveRuleTextSelection"
                          />
                        </div>
                      </Space>
                      <Space wrap>
                        <Button
                          size="small"
                          @mousedown.prevent="applyRuleTextCommand('bold')"
                        >
                          <strong>B</strong>
                        </Button>
                        <Button
                          size="small"
                          @mousedown.prevent="applyRuleTextCommand('italic')"
                        >
                          <em>I</em>
                        </Button>
                        <Button
                          size="small"
                          @mousedown.prevent="applyRuleTextCommand('underline')"
                        >
                          <u>U</u>
                        </Button>
                        <Button
                          size="small"
                          @mousedown.prevent="
                            applyRuleTextCommand('insertUnorderedList')
                          "
                        >
                          无序列表
                        </Button>
                        <Button
                          size="small"
                          @mousedown.prevent="
                            applyRuleTextCommand('insertOrderedList')
                          "
                        >
                          有序列表
                        </Button>
                        <Button size="small" @mousedown.prevent="clearRuleTextFormat">
                          清除格式
                        </Button>
                      </Space>
                    </div>
                    <div
                      ref="ruleEditorRef"
                      class="rule-editor"
                      contenteditable="true"
                      data-placeholder="请输入规则说明"
                      @blur="syncRuleTextFromEditor()"
                      @keyup="saveRuleTextSelection"
                      @input="syncRuleTextFromEditor()"
                      @mouseup="saveRuleTextSelection"
                      @paste="handleRuleTextPaste"
                    ></div>
                  </div>
                </div>
              </div>

              <div class="actions">
                <Space>
                  <Button @click="loadConfig">刷新</Button>
                  <Button type="primary" :loading="configSaving" @click="saveConfig">
                    保存基础配置
                  </Button>
                </Space>
              </div>
            </Card>
          </Spin>
        </TabPane>

        <TabPane key="levels" tab="等级进度">
          <Card>
            <Table
              :columns="levelColumns"
              :data-source="levelRows"
              :loading="levelLoading"
              :pagination="false"
              :scroll="{ x: 1500 }"
              row-key="level"
            >
              <template #bodyCell="{ column, record }">
                <template v-if="column.key === 'level'">
                  <Tag color="blue">Lv.{{ record.level }}</Tag>
                </template>
                <template v-else-if="column.key === 'needEnergy'">
                  <InputNumber
                    v-model:value="record.needEnergy"
                    :min="1"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'previewRocketUrl'">
                  <div class="resource-cell">
                    <div class="resource-cell__preview">
                      <img
                        v-if="record.previewRocketUrl"
                        :src="record.previewRocketUrl"
                        alt=""
                      />
                      <span>{{ resourceUrlName(record.previewRocketUrl) }}</span>
                    </div>
                    <Space>
                      <Button
                        size="small"
                        type="primary"
                        @click="
                          openLevelResourcePicker(record, 'previewRocketUrl')
                        "
                      >
                        {{ record.previewRocketUrl ? '更换资源' : '选择资源' }}
                      </Button>
                      <Button
                        v-if="record.previewRocketUrl"
                        size="small"
                        @click="clearLevelResource(record, 'previewRocketUrl')"
                      >
                        清除
                      </Button>
                    </Space>
                  </div>
                </template>
                <template v-else-if="column.key === 'rocketIconUrl'">
                  <div class="resource-cell">
                    <div class="resource-cell__preview">
                      <img
                        v-if="record.rocketIconUrl"
                        :src="record.rocketIconUrl"
                        alt=""
                      />
                      <span>{{ resourceUrlName(record.rocketIconUrl) }}</span>
                    </div>
                    <Space>
                      <Button
                        size="small"
                        type="primary"
                        @click="openLevelResourcePicker(record, 'rocketIconUrl')"
                      >
                        {{ record.rocketIconUrl ? '更换资源' : '选择资源' }}
                      </Button>
                      <Button
                        v-if="record.rocketIconUrl"
                        size="small"
                        @click="clearLevelResource(record, 'rocketIconUrl')"
                      >
                        清除
                      </Button>
                    </Space>
                  </div>
                </template>
                <template v-else-if="column.key === 'rocketAnimationUrl'">
                  <div class="resource-cell">
                    <div class="resource-cell__preview">
                      <img
                        v-if="record.rocketAnimationUrl"
                        :src="record.rocketAnimationUrl"
                        alt=""
                      />
                      <span>{{ resourceUrlName(record.rocketAnimationUrl) }}</span>
                    </div>
                    <Space>
                      <Button
                        size="small"
                        type="primary"
                        @click="
                          openLevelResourcePicker(record, 'rocketAnimationUrl')
                        "
                      >
                        {{ record.rocketAnimationUrl ? '更换资源' : '选择资源' }}
                      </Button>
                      <Button
                        v-if="record.rocketAnimationUrl"
                        size="small"
                        @click="clearLevelResource(record, 'rocketAnimationUrl')"
                      >
                        清除
                      </Button>
                    </Space>
                  </div>
                </template>
                <template v-else-if="column.key === 'shakeThresholdPercent'">
                  <Input
                    v-model:value="record.shakeThresholdPercent"
                    class="table-input"
                  />
                </template>
                <template v-else-if="column.key === 'enabled'">
                  <Switch v-model:checked="record.enabled" />
                </template>
                <template v-else-if="column.key === 'sort'">
                  <InputNumber
                    v-model:value="record.sort"
                    :min="1"
                    :precision="0"
                    class="table-number"
                  />
                </template>
              </template>
            </Table>

            <div class="actions">
              <Space>
                <Button @click="loadLevels">刷新</Button>
                <Button type="primary" :loading="levelSaving" @click="saveLevels">
                  保存等级进度
                </Button>
              </Space>
            </div>
          </Card>
        </TabPane>

        <TabPane key="rewards" tab="奖励配置">
          <Card>
            <Space class="reward-toolbar" wrap>
              <span>等级</span>
              <Select
                v-model:value="rewardLevel"
                :options="levelOptions"
                style="width: 120px"
              />
              <Button @click="loadRewards">刷新</Button>
              <Button @click="addRewardRow('TOP1')">添加贡献第一</Button>
              <Button @click="addRewardRow('IGNITE')">添加点火人</Button>
              <Button @click="addRewardRow('IN_ROOM')">添加在房用户</Button>
              <Button @click="addRewardRow('IN_ROOM', 'NONE')">添加在房轮空</Button>
              <Button type="primary" :loading="rewardSaving" @click="saveRewards">
                保存当前等级奖励
              </Button>
            </Space>

            <Table
              :columns="rewardColumns"
              :data-source="rewardRows"
              :loading="rewardLoading"
              :pagination="false"
              :scroll="{ x: 1770 }"
              row-key="localKey"
            >
              <template #bodyCell="{ column, record, index }">
                <template v-if="column.key === 'rewardScene'">
                  <Select
                    v-model:value="record.rewardScene"
                    :options="sceneOptions"
                    class="table-select"
                    @change="() => handleRewardSceneChange(record)"
                  />
                </template>
                <template v-else-if="column.key === 'rewardType'">
                  <Select
                    v-model:value="record.rewardType"
                    :options="rewardTypeOptionsForRow(record)"
                    class="table-select"
                    @change="() => handleRewardTypeChange(record)"
                  />
                </template>
                <template v-else-if="column.key === 'rewardItemId'">
                  <div class="resource-cell">
                    <div class="resource-cell__summary">
                      <span v-if="isRewardResourceDisabled(record)">-</span>
                      <span v-else-if="record.rewardItemId">
                        {{ record.rewardName || '已选资源' }} / ID:
                        {{ record.rewardItemId }}
                      </span>
                      <span v-else>未选择</span>
                    </div>
                    <Space v-if="!isRewardResourceDisabled(record)">
                      <Button
                        size="small"
                        type="primary"
                        @click="openRewardResourcePicker(record)"
                      >
                        {{ record.rewardItemId ? '更换资源' : '选择资源' }}
                      </Button>
                      <Button
                        v-if="record.rewardItemId"
                        size="small"
                        @click="clearRewardResource(record)"
                      >
                        清除
                      </Button>
                    </Space>
                  </div>
                </template>
                <template v-else-if="column.key === 'rewardName'">
                  <Input
                    v-model:value="record.rewardName"
                    :disabled="record.rewardType === 'NONE'"
                    class="table-input-small"
                  />
                </template>
                <template v-else-if="column.key === 'rewardCover'">
                  <div class="cover-preview">
                    <img
                      v-if="record.rewardCover"
                      :src="record.rewardCover"
                      alt=""
                    />
                    <span>{{ record.rewardCover || '-' }}</span>
                  </div>
                </template>
                <template v-else-if="column.key === 'rewardAmount'">
                  <InputNumber
                    v-model:value="record.rewardAmount"
                    :disabled="record.rewardType === 'NONE'"
                    :min="record.rewardType === 'NONE' ? 0 : 1"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'expireDays'">
                  <InputNumber
                    v-model:value="record.expireDays"
                    :disabled="
                      record.rewardType === 'GOLD' || record.rewardType === 'NONE'
                    "
                    :min="0"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'rewardCopies'">
                  <InputNumber
                    v-model:value="record.rewardCopies"
                    :disabled="record.rewardScene !== 'IN_ROOM'"
                    :min="1"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'weight'">
                  <InputNumber
                    v-model:value="record.weight"
                    :min="0"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'enabled'">
                  <Switch v-model:checked="record.enabled" />
                </template>
                <template v-else-if="column.key === 'sort'">
                  <InputNumber
                    v-model:value="record.sort"
                    :min="1"
                    :precision="0"
                    class="table-number"
                  />
                </template>
                <template v-else-if="column.key === 'actions'">
                  <Button danger size="small" @click="removeRewardRow(index)">
                    删除
                  </Button>
                </template>
              </template>
            </Table>

            <div class="reward-summary">
              <Tag v-for="scene in sceneOptions" :key="scene.value">
                {{ scene.label }}：
                {{
                  rewardRows.filter((item) => item.rewardScene === scene.value).length
                }}
              </Tag>
              <Tag v-for="type in rewardTypeOptions" :key="type.value">
                {{ type.label }}：
                {{ rewardRows.filter((item) => item.rewardType === type.value).length }}
              </Tag>
            </div>
          </Card>
        </TabPane>
      </Tabs>

      <ResourceSelectDrawer
        :default-type="resourcePickerDefaultType"
        :fixed-type="resourcePickerFixedType"
        :open="resourcePickerOpen"
        :sys-origin="selectedSysOrigin"
        :title="resourcePickerTitle"
        @close="resourcePickerOpen = false"
        @select="handleResourceSelect"
      />
    </div>
  </Page>
</template>

<style scoped>
.voice-room-rocket-page {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.config-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
}

.field-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13px;
  color: #4b5563;
}

.field-wide {
  grid-column: 1 / -1;
}

.field-control {
  width: 100%;
}

.rule-editor-panel {
  overflow: hidden;
  width: 100%;
  border: 1px solid #d9d9d9;
  border-radius: 6px;
  background: #fff;
}

.rule-editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  padding: 8px;
  border-bottom: 1px solid #f0f0f0;
}

.rule-editor-locale {
  width: 120px;
}

.rule-editor-size {
  width: 96px;
}

.rule-editor-colors {
  display: inline-flex;
  gap: 6px;
  align-items: center;
}

.rule-color-swatch {
  width: 22px;
  height: 22px;
  padding: 0;
  cursor: pointer;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
}

.rule-color-swatch--active {
  border-color: #1677ff;
  box-shadow: 0 0 0 2px rgb(22 119 255 / 16%);
}

.rule-color-picker {
  width: 28px;
  height: 24px;
  padding: 0;
  cursor: pointer;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
}

.rule-editor {
  min-height: 180px;
  max-height: 360px;
  padding: 12px;
  overflow: auto;
  color: #111827;
  outline: none;
}

.rule-editor:empty::before {
  color: #bfbfbf;
  content: attr(data-placeholder);
  pointer-events: none;
}

.actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}

.muted-text {
  color: #8c8c8c;
}

.reward-toolbar {
  margin-bottom: 16px;
}

.reward-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.table-input {
  width: 240px;
}

.table-input-small,
.table-select {
  width: 140px;
}

.table-number {
  width: 110px;
}

.resource-cell {
  display: grid;
  gap: 8px;
  min-width: 220px;
}

.resource-cell__preview {
  align-items: center;
  color: #6b7280;
  display: flex;
  font-size: 12px;
  gap: 8px;
  min-height: 36px;
}

.resource-cell__preview img {
  border-radius: 4px;
  flex: 0 0 auto;
  height: 32px;
  object-fit: cover;
  width: 32px;
}

.resource-cell__preview span,
.resource-cell__summary {
  color: #6b7280;
  font-size: 12px;
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cover-preview {
  display: flex;
  align-items: center;
  max-width: 220px;
  gap: 8px;
  color: #6b7280;
  font-size: 12px;
}

.cover-preview img {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 4px;
  object-fit: cover;
}

.cover-preview span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
</style>
