import { ReloadOutlined, SaveOutlined } from '@ant-design/icons';
import { Alert, App, Button, Checkbox, Form, Input, InputNumber, Radio, Select, Spin, Tabs, Typography } from 'antd';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { fetchWebPushConfig } from './notificationApi';
import { NOTIFICATION_DEVICE_ID_STORAGE_KEY, getSavedNotificationDeviceId } from './notificationIdentity';
import {
  DEFAULT_APP_CONFIG,
  APP_CONFIG_STORAGE_KEY,
  saveAppConfigToServer,
  type AppConfig,
} from './appConfig';
import {
  TOKEN_ACCESS_STORAGE_KEY,
} from './tokenAccess';
import {
  clearWebPushSubscriptionRegistration,
  ensureWebPushSubscriptionRegistered,
  syncExistingWebPushSubscriptionRegistration,
} from './webPushRegistration';
import { CODEX_MODEL_OPTIONS, normalizeCodexModel } from './codexModelOptions';
import { isPreviewRuntime } from './previewRuntime';
import './SharedAppSettingsPage.css';

const { Paragraph, Text, Title } = Typography;

const WEB_PUSH_METADATA_STORAGE_KEY = 'work-server.web-push.registration-meta.v1';
const SHARE_IMMEDIATE_SEND_PINNED_STORAGE_KEY = 'codex-live-share-immediate-send-pinned-by-token';
const SHARE_LAST_ROOM_STORAGE_KEY = 'codex-live-share-last-room-by-token';
const SHARE_APP_LAUNCH_USAGE_STORAGE_KEY = 'chat-share-page:app-launch-usage:v1';
const CHAT_SHARE_ACCESS_PIN_STORAGE_KEY = 'main-chat-panel:share-access-pins';
const CHAT_CONTEXT_CONFIRM_SUPPRESSION_STORAGE_KEY = 'codex-live-context-confirm-suppressed-by-session';
const PREVIEW_RUNTIME_TOKEN_STORAGE_KEY = 'work-app.preview-runtime.registered-token';

type SharedAppSettingsPageProps = {
  shareToken: string;
};

type SharedAppSettingsFormValue = AppConfig;
type ClientNotificationPermissionState = 'granted' | 'denied' | 'default' | 'unsupported';
type StorageScope = 'local' | 'session';

type StorageEntry = {
  id: string;
  label: string;
  description: string;
  scope: StorageScope;
  storageKey: string;
};

type StorageGroup = {
  key: string;
  label: string;
  description: string;
  entries: StorageEntry[];
};

const STORAGE_GROUPS: StorageGroup[] = [
  {
    key: 'share-chat',
    label: '공유채팅',
    description: '공유채팅 화면에서 쓰는 클라이언트 저장값입니다.',
    entries: [
      {
        id: 'share-last-room',
        label: '마지막 채팅방',
        description: '토큰별 마지막으로 열었던 채팅방 세션을 기억합니다.',
        scope: 'local',
        storageKey: SHARE_LAST_ROOM_STORAGE_KEY,
      },
      {
        id: 'share-immediate-send',
        label: '즉시전송 고정',
        description: '토큰별 즉시전송 고정 상태를 저장합니다.',
        scope: 'local',
        storageKey: SHARE_IMMEDIATE_SEND_PINNED_STORAGE_KEY,
      },
      {
        id: 'share-app-usage',
        label: '앱 실행 우선순위',
        description: '공유채팅 안에서 자주 실행한 앱 사용량을 기록합니다.',
        scope: 'local',
        storageKey: SHARE_APP_LAUNCH_USAGE_STORAGE_KEY,
      },
      {
        id: 'share-access-pin',
        label: '공유 비밀번호 캐시',
        description: '방별 접근 비밀번호 입력 이력을 저장합니다.',
        scope: 'local',
        storageKey: CHAT_SHARE_ACCESS_PIN_STORAGE_KEY,
      },
      {
        id: 'context-confirm',
        label: '문맥 확인 숨김',
        description: '세션별 문맥 확인 안내를 다시 보지 않도록 저장합니다.',
        scope: 'local',
        storageKey: CHAT_CONTEXT_CONFIRM_SUPPRESSION_STORAGE_KEY,
      },
    ],
  },
  {
    key: 'notifications',
    label: '알림 / PWA',
    description: '웹 푸시와 기기 등록에 쓰는 저장값입니다.',
    entries: [
      {
        id: 'notification-device-id',
        label: '알림 기기 ID',
        description: '현재 브라우저를 식별하는 웹 푸시 기기 ID입니다.',
        scope: 'local',
        storageKey: NOTIFICATION_DEVICE_ID_STORAGE_KEY,
      },
      {
        id: 'web-push-meta',
        label: '웹 푸시 등록 메타',
        description: '최근 웹 푸시 등록 상태와 메타데이터를 저장합니다.',
        scope: 'local',
        storageKey: WEB_PUSH_METADATA_STORAGE_KEY,
      },
    ],
  },
  {
    key: 'access',
    label: '접근 토큰',
    description: '앱 접근과 등록 토큰에 쓰는 저장값입니다.',
    entries: [
      {
        id: 'registered-token',
        label: '등록 토큰',
        description: '앱 접근 허용 토큰을 로컬에 저장합니다.',
        scope: 'local',
        storageKey: TOKEN_ACCESS_STORAGE_KEY,
      },
      {
        id: 'preview-token',
        label: '미리보기 토큰',
        description: 'preview 런타임 등록 토큰을 저장합니다.',
        scope: 'local',
        storageKey: PREVIEW_RUNTIME_TOKEN_STORAGE_KEY,
      },
      {
        id: 'app-config',
        label: '앱 설정 캐시',
        description: '서버에서 받은 앱 설정의 마지막 캐시입니다.',
        scope: 'local',
        storageKey: APP_CONFIG_STORAGE_KEY,
      },
    ],
  },
];

function getClientNotificationPermission(): ClientNotificationPermissionState {
  if (
    typeof window === 'undefined'
    || typeof Notification === 'undefined'
    || typeof navigator === 'undefined'
    || !('serviceWorker' in navigator)
    || !('PushManager' in window)
  ) {
    return 'unsupported';
  }

  if (Notification.permission === 'granted') {
    return 'granted';
  }

  if (Notification.permission === 'denied') {
    return 'denied';
  }

  return 'default';
}

function getClientNotificationPermissionLabel(permission: ClientNotificationPermissionState) {
  switch (permission) {
    case 'granted':
      return '허용';
    case 'denied':
      return '차단';
    case 'unsupported':
      return '미지원';
    default:
      return '확인 필요';
  }
}

function hasSecureOrigin() {
  if (typeof window === 'undefined') {
    return false;
  }

  return window.isSecureContext || window.location.hostname === 'localhost';
}

function isStandaloneDisplayMode() {
  if (typeof window === 'undefined') {
    return false;
  }

  return (
    window.matchMedia?.('(display-mode: standalone)').matches === true ||
    (window.navigator as Navigator & { standalone?: boolean }).standalone === true
  );
}

function isAppleMobileDevice() {
  if (typeof navigator === 'undefined') {
    return false;
  }

  return /iPhone|iPad|iPod/i.test(navigator.userAgent);
}

async function getPushServiceWorkerRegistration() {
  if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {
    return null;
  }

  const resolveUsableRegistration = async (registration: ServiceWorkerRegistration | null | undefined) => {
    if (!registration) {
      return null;
    }

    if (registration.active || registration.waiting) {
      return registration;
    }

    const installingWorker = registration.installing;

    if (!installingWorker) {
      return registration;
    }

    await new Promise<void>((resolve) => {
      let completed = false;

      const finish = () => {
        if (completed) {
          return;
        }

        completed = true;
        window.clearTimeout(timeoutId);
        installingWorker.removeEventListener('statechange', onStateChange);
        resolve();
      };

      const onStateChange = () => {
        if (
          installingWorker.state === 'activated'
          || installingWorker.state === 'installed'
          || Boolean(registration.active)
          || Boolean(registration.waiting)
        ) {
          finish();
        }
      };

      const timeoutId = window.setTimeout(finish, 5000);
      installingWorker.addEventListener('statechange', onStateChange);
      onStateChange();
    });

    return registration;
  };

  const existing = await navigator.serviceWorker.getRegistration();
  if (existing) {
    return (await resolveUsableRegistration(existing)) ?? existing;
  }

  const scoped = await navigator.serviceWorker.getRegistration('/');
  if (scoped) {
    return (await resolveUsableRegistration(scoped)) ?? scoped;
  }

  const serviceWorkerUrl = import.meta.env.DEV ? '/dev-sw.js?dev-sw' : '/sw.js';
  const resolvedServiceWorkerUrl =
    typeof window !== 'undefined' ? new URL(serviceWorkerUrl, window.location.href).toString() : serviceWorkerUrl;

  try {
    const registration = import.meta.env.DEV
      ? await navigator.serviceWorker.register(resolvedServiceWorkerUrl, { type: 'module', scope: '/', updateViaCache: 'none' })
        .catch(() => navigator.serviceWorker.register(resolvedServiceWorkerUrl, { scope: '/', updateViaCache: 'none' }))
      : await navigator.serviceWorker.register(resolvedServiceWorkerUrl);
    return (await resolveUsableRegistration(registration)) ?? registration;
  } catch {
    return null;
  }
}

function readStorageValue(scope: StorageScope, key: string) {
  if (typeof window === 'undefined') {
    return '';
  }

  try {
    const storage = scope === 'local' ? window.localStorage : window.sessionStorage;
    return storage.getItem(key) ?? '';
  } catch {
    return '';
  }
}

function writeStorageValue(scope: StorageScope, key: string, value: string) {
  if (typeof window === 'undefined') {
    return;
  }

  const storage = scope === 'local' ? window.localStorage : window.sessionStorage;

  if (!value.trim()) {
    storage.removeItem(key);
    return;
  }

  storage.setItem(key, value);
}

function buildStorageItemKey(entry: StorageEntry) {
  return `${entry.scope}:${entry.storageKey}`;
}

function formatRoomModelByRoomValue(value: AppConfig['chat']['defaultModelByRoom']) {
  return JSON.stringify(value || {}, null, 2);
}

function parseRoomModelByRoomValue(raw: string) {
  const trimmed = raw.trim();

  if (!trimmed) {
    return {};
  }

  const parsed = JSON.parse(trimmed) as Record<string, unknown>;

  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error('채팅방별 모델 설정은 JSON 객체 형태여야 합니다.');
  }

  return Object.entries(parsed).reduce<Record<string, string>>((accumulator, [sessionId, modelValue]) => {
    const normalizedSessionId = sessionId.trim();

    if (!normalizedSessionId) {
      return accumulator;
    }

    const normalizedModel =
      typeof modelValue === 'string'
        ? normalizeCodexModel(modelValue)
        : modelValue == null
          ? ''
          : normalizeCodexModel(String(modelValue));

    if (!normalizedModel) {
      return accumulator;
    }

    accumulator[normalizedSessionId] = normalizedModel;
    return accumulator;
  }, {});
}

export function SharedAppSettingsPage({ shareToken }: SharedAppSettingsPageProps) {
  const { message } = App.useApp();
  const [form] = Form.useForm<SharedAppSettingsFormValue>();
  const [isLoading, setIsLoading] = useState(true);
  const [isSaving, setIsSaving] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');
  const [savedConfig, setSavedConfig] = useState<AppConfig>(DEFAULT_APP_CONFIG);
  const [roomModelByRoomText, setRoomModelByRoomText] = useState(() => formatRoomModelByRoomValue(DEFAULT_APP_CONFIG.chat.defaultModelByRoom));
  const [activeTabKey, setActiveTabKey] = useState('general');
  const [storageGroupKey, setStorageGroupKey] = useState(STORAGE_GROUPS[0]?.key ?? 'share-chat');
  const [storageDrafts, setStorageDrafts] = useState<Record<string, string>>({});
  const [storageFeedback, setStorageFeedback] = useState('');
  const [webPushConfigured, setWebPushConfigured] = useState(false);
  const [webPushRegistered, setWebPushRegistered] = useState(false);
  const [webPushLoading, setWebPushLoading] = useState(false);
  const [clientPermission, setClientPermission] = useState<ClientNotificationPermissionState>(() => getClientNotificationPermission());

  const selectedStorageGroup = useMemo(
    () => STORAGE_GROUPS.find((group) => group.key === storageGroupKey) ?? STORAGE_GROUPS[0],
    [storageGroupKey],
  );

  const loadStorageDrafts = useCallback(() => {
    const nextDrafts = STORAGE_GROUPS.flatMap((group) => group.entries).reduce<Record<string, string>>((accumulator, entry) => {
      accumulator[buildStorageItemKey(entry)] = readStorageValue(entry.scope, entry.storageKey);
      return accumulator;
    }, {});
    setStorageDrafts(nextDrafts);
  }, []);

  const syncWebPushStatus = useCallback(async () => {
    setClientPermission(getClientNotificationPermission());

    try {
      const config = await fetchWebPushConfig();
      const isConfigured = Boolean(config.enabled && config.publicKey);
      setWebPushConfigured(isConfigured);

      if (!isConfigured) {
        setWebPushRegistered(false);
        return;
      }

      const registration = await getPushServiceWorkerRegistration();

      if (!registration || typeof Notification === 'undefined' || Notification.permission !== 'granted') {
        setWebPushRegistered(false);
        return;
      }

      const subscription = await registration.pushManager.getSubscription();

      if (!subscription) {
        setWebPushRegistered(false);
        return;
      }

      await syncExistingWebPushSubscriptionRegistration(registration, {
        deviceId: getSavedNotificationDeviceId(),
      });
      setWebPushRegistered(true);
    } catch {
      setWebPushConfigured(false);
      setWebPushRegistered(false);
    }
  }, []);

  const loadConfig = useCallback(async () => {
    setIsLoading(true);
    setErrorMessage('');

    try {
      const response = await fetch('/api/app-config', {
        headers: {
          'X-Chat-Share-Token': shareToken,
        },
        cache: 'no-store',
      });

      if (!response.ok) {
        const payload = (await response.json().catch(() => null)) as { message?: string } | null;
        throw new Error(payload?.message || '앱 설정을 불러오지 못했습니다.');
      }

      const payload = (await response.json()) as { config?: AppConfig };
      const nextConfig = payload.config ?? DEFAULT_APP_CONFIG;
      setSavedConfig(nextConfig);
      form.setFieldsValue(nextConfig);
      setRoomModelByRoomText(formatRoomModelByRoomValue(nextConfig.chat.defaultModelByRoom));
      await syncWebPushStatus();
      loadStorageDrafts();
    } catch (error) {
      setErrorMessage(error instanceof Error ? error.message : '앱 설정을 불러오지 못했습니다.');
    } finally {
      setIsLoading(false);
    }
  }, [form, loadStorageDrafts, shareToken, syncWebPushStatus]);

  useEffect(() => {
    void loadConfig();
  }, [loadConfig]);

  const handleSave = useCallback(
    async (values: SharedAppSettingsFormValue) => {
      setIsSaving(true);
      setErrorMessage('');

      try {
        const normalizedValues: SharedAppSettingsFormValue = {
          ...values,
          chat: {
            ...values.chat,
            defaultModelByRoom: parseRoomModelByRoomValue(roomModelByRoomText),
            defaultModelGlobal: normalizeCodexModel(values.chat.defaultModelGlobal),
          },
        };

        const saved = await saveAppConfigToServer(normalizedValues, {
          shareToken,
          skipAutomationNotifications: true,
        });
        setSavedConfig(saved);
        form.setFieldsValue(saved);
        setRoomModelByRoomText(formatRoomModelByRoomValue(saved.chat.defaultModelByRoom));
        message.success('개인설정을 저장했습니다.');
      } catch (error) {
        setErrorMessage(error instanceof Error ? error.message : '앱 설정 저장에 실패했습니다.');
      } finally {
        setIsSaving(false);
      }
    },
    [form, message, roomModelByRoomText, shareToken],
  );

  const handleRequestNotificationPermission = useCallback(async () => {
    if (typeof Notification === 'undefined') {
      message.warning('현재 브라우저에서는 알림 권한 요청을 지원하지 않습니다.');
      return;
    }

    if (!hasSecureOrigin()) {
      message.warning('알림은 HTTPS 또는 localhost 환경에서만 권한 요청이 가능합니다.');
      return;
    }

    if (isAppleMobileDevice() && !isStandaloneDisplayMode()) {
      message.warning('아이폰에서는 홈 화면에 추가한 PWA에서만 웹 푸시를 허용할 수 있습니다.');
      return;
    }

    try {
      await Notification.requestPermission();
      await syncWebPushStatus();
      message.success('브라우저 알림 권한 상태를 다시 확인했습니다.');
    } catch {
      message.error('알림 권한 요청 중 오류가 발생했습니다.');
    }
  }, [message, syncWebPushStatus]);

  const handleRegisterWebPush = useCallback(async () => {
    if (isPreviewRuntime()) {
      message.warning('미리보기 런타임에서는 웹 푸시 등록을 지원하지 않습니다.');
      return;
    }

    if (!hasSecureOrigin()) {
      message.warning('웹 푸시는 HTTPS 또는 localhost 환경에서만 등록할 수 있습니다.');
      return;
    }

    if (isAppleMobileDevice() && !isStandaloneDisplayMode()) {
      message.warning('아이폰에서는 홈 화면에 추가한 PWA로 연 뒤 등록해 주세요.');
      return;
    }

    setWebPushLoading(true);

    try {
      let permission = getClientNotificationPermission();

      if (permission !== 'granted' && typeof Notification !== 'undefined') {
        await Notification.requestPermission();
        permission = getClientNotificationPermission();
      }

      if (permission !== 'granted') {
        throw new Error('브라우저 알림 권한을 허용한 뒤 다시 시도해 주세요.');
      }

      const registration = await getPushServiceWorkerRegistration();

      if (!registration) {
        throw new Error('서비스워커 준비가 아직 끝나지 않았습니다. 잠시 후 다시 시도해 주세요.');
      }

      await ensureWebPushSubscriptionRegistered(registration, {
        deviceId: getSavedNotificationDeviceId(),
      });
      await syncWebPushStatus();
      loadStorageDrafts();
      message.success('이 기기의 웹 푸시 등록을 완료했습니다.');
    } catch (error) {
      message.error(error instanceof Error ? error.message : '웹 푸시 등록에 실패했습니다.');
    } finally {
      setWebPushLoading(false);
    }
  }, [loadStorageDrafts, message, syncWebPushStatus]);

  const handleUnregisterWebPush = useCallback(async () => {
    setWebPushLoading(true);

    try {
      const registration = await getPushServiceWorkerRegistration();

      if (!registration) {
        throw new Error('해제할 서비스워커 등록을 찾지 못했습니다.');
      }

      await clearWebPushSubscriptionRegistration(registration);
      await syncWebPushStatus();
      loadStorageDrafts();
      message.success('이 기기의 웹 푸시 등록을 해제했습니다.');
    } catch (error) {
      message.error(error instanceof Error ? error.message : '웹 푸시 해제에 실패했습니다.');
    } finally {
      setWebPushLoading(false);
    }
  }, [loadStorageDrafts, message, syncWebPushStatus]);

  const handleStorageDraftChange = useCallback((entry: StorageEntry, value: string) => {
    setStorageDrafts((current) => ({
      ...current,
      [buildStorageItemKey(entry)]: value,
    }));
  }, []);

  const handleReloadStorageEntry = useCallback((entry: StorageEntry) => {
    setStorageFeedback('');
    setStorageDrafts((current) => ({
      ...current,
      [buildStorageItemKey(entry)]: readStorageValue(entry.scope, entry.storageKey),
    }));
  }, []);

  const handleSaveStorageEntry = useCallback((entry: StorageEntry) => {
    try {
      const draftValue = storageDrafts[buildStorageItemKey(entry)] ?? '';
      writeStorageValue(entry.scope, entry.storageKey, draftValue);
      setStorageFeedback(`${entry.label} 저장값을 반영했습니다.`);
      message.success(`${entry.label} 저장값을 반영했습니다.`);
    } catch (error) {
      const nextMessage = error instanceof Error ? error.message : `${entry.label} 저장값을 반영하지 못했습니다.`;
      setStorageFeedback(nextMessage);
      message.error(nextMessage);
    }
  }, [message, storageDrafts]);

  const handleClearStorageEntry = useCallback((entry: StorageEntry) => {
    try {
      writeStorageValue(entry.scope, entry.storageKey, '');
      setStorageDrafts((current) => ({
        ...current,
        [buildStorageItemKey(entry)]: '',
      }));
      setStorageFeedback(`${entry.label} 저장값을 비웠습니다.`);
      message.success(`${entry.label} 저장값을 비웠습니다.`);
    } catch (error) {
      const nextMessage = error instanceof Error ? error.message : `${entry.label} 저장값을 비우지 못했습니다.`;
      setStorageFeedback(nextMessage);
      message.error(nextMessage);
    }
  }, [message]);

  if (isLoading) {
    return (
      <div className="shared-app-settings-page shared-app-settings-page--loading">
        <Spin size="large" />
      </div>
    );
  }

  return (
    <div className="shared-app-settings-page">
      <div className="shared-app-settings-page__header">
        <div>
          <Title level={4}>개인설정</Title>
          <Paragraph type="secondary">
            공유채팅 안에서 개인 단위 앱 설정, 웹 푸시 등록, 클라이언트 저장값을 한 번에 관리합니다.
          </Paragraph>
        </div>
        <div className="shared-app-settings-page__header-actions">
          <Button icon={<ReloadOutlined />} onClick={() => void loadConfig()} disabled={isSaving || webPushLoading}>
            새로고침
          </Button>
          <Button type="primary" icon={<SaveOutlined />} onClick={() => void form.submit()} loading={isSaving}>
            저장
          </Button>
        </div>
      </div>

      {errorMessage ? <Alert showIcon type="error" message={errorMessage} /> : null}

      <Form<SharedAppSettingsFormValue>
        form={form}
        layout="vertical"
        initialValues={savedConfig}
        onFinish={(values) => void handleSave(values)}
      >
        <Tabs
          activeKey={activeTabKey}
          onChange={setActiveTabKey}
          className="shared-app-settings-page__tabs"
          items={[
            {
              key: 'general',
              label: '기본',
              children: (
                <div className="shared-app-settings-page__panel">
                  <div className="shared-app-settings-page__section">
                    <div className="shared-app-settings-page__section-head">
                      <Text strong>채팅 기본값</Text>
                      <Text type="secondary">공유채팅과 Codex Live 동작에 직접 연결되는 최소 설정만 다룹니다.</Text>
                    </div>
                    <div className="shared-app-settings-page__field-grid">
                      <Form.Item label="최근 메시지 수" name={['chat', 'maxContextMessages']}>
                        <InputNumber min={1} max={50} style={{ width: '100%' }} />
                      </Form.Item>
                      <Form.Item label="최대 문자 수" name={['chat', 'maxContextChars']}>
                        <InputNumber min={500} max={20000} step={100} style={{ width: '100%' }} />
                      </Form.Item>
                      <Form.Item label="최대 실행 시간(초)" name={['chat', 'codexLiveMaxExecutionSeconds']}>
                        <InputNumber min={60} max={7200} step={30} style={{ width: '100%' }} />
                      </Form.Item>
                      <Form.Item label="무출력 실패 시간(초)" name={['chat', 'codexLiveIdleTimeoutSeconds']}>
                        <InputNumber min={30} max={3600} step={10} style={{ width: '100%' }} />
                      </Form.Item>
                      <Form.Item label="재기동 완료 자동 실행 대기(초)" name={['chat', 'restartReservationCompletionDelaySeconds']}>
                        <InputNumber min={1} max={300} style={{ width: '100%' }} />
                      </Form.Item>
                      <Form.Item label="통합 검색 단축키" name={['gestureShortcuts', 'openSearch']}>
                        <Input />
                      </Form.Item>
                      <Form.Item label="모델 적용 범위" name={['chat', 'defaultModelScope']}>
                        <Radio.Group
                          options={[
                            { label: '전체', value: 'all' },
                            { label: '채팅방별', value: 'room' },
                          ]}
                        />
                      </Form.Item>
                      <Form.Item label="전체 기본 모델" name={['chat', 'defaultModelGlobal']}>
                        <Select
                          options={CODEX_MODEL_OPTIONS.map((option) => ({
                            label: option.label,
                            value: option.value,
                          }))}
                        />
                      </Form.Item>
                      <Form.Item label="채팅방별 마지막 모델(JSON)">
                        <Input.TextArea
                          rows={8}
                          value={roomModelByRoomText}
                          onChange={(event) => setRoomModelByRoomText(event.target.value)}
                          placeholder='{}'
                        />
                      </Form.Item>
                    </div>
                  </div>

                  <div className="shared-app-settings-page__section">
                    <div className="shared-app-settings-page__section-head">
                      <Text strong>알림 유도 문구</Text>
                      <Text type="secondary">앱 전체 웹 푸시 등록 흐름에서 PWA, 권한, 등록 안내를 어떻게 유도할지 정합니다.</Text>
                    </div>
                    <div className="shared-app-settings-page__check-grid">
                      <Form.Item name={['chat', 'receiveRoomNotifications']} valuePropName="checked">
                        <Checkbox>앱 전체 채팅방 알림 수신 사용</Checkbox>
                      </Form.Item>
                      <Form.Item name={['chat', 'guidePwaInstallForNotifications']} valuePropName="checked">
                        <Checkbox>PWA 설치 유도 사용</Checkbox>
                      </Form.Item>
                      <Form.Item name={['chat', 'guideWebPushPermission']} valuePropName="checked">
                        <Checkbox>브라우저 권한 유도 사용</Checkbox>
                      </Form.Item>
                      <Form.Item name={['chat', 'guideWebPushRegistration']} valuePropName="checked">
                        <Checkbox>웹 푸시 등록 유도 사용</Checkbox>
                      </Form.Item>
                    </div>
                  </div>
                </div>
              ),
            },
            {
              key: 'web-push',
              label: '웹 푸시',
              children: (
                <div className="shared-app-settings-page__panel">
                  <div className="shared-app-settings-page__section">
                    <div className="shared-app-settings-page__section-head">
                      <Text strong>현재 기기 등록 상태</Text>
                      <Text type="secondary">권한, PWA 실행 여부, 서버 설정, 현재 기기 등록 여부를 바로 확인합니다.</Text>
                    </div>
                    <div className="shared-app-settings-page__status-grid">
                      <div className="shared-app-settings-page__status-item">
                        <span>권한</span>
                        <strong>{getClientNotificationPermissionLabel(clientPermission)}</strong>
                      </div>
                      <div className="shared-app-settings-page__status-item">
                        <span>PWA</span>
                        <strong>{isStandaloneDisplayMode() ? '실행 중' : '브라우저 탭'}</strong>
                      </div>
                      <div className="shared-app-settings-page__status-item">
                        <span>보안 환경</span>
                        <strong>{hasSecureOrigin() ? 'HTTPS 준비' : '등록 불가'}</strong>
                      </div>
                      <div className="shared-app-settings-page__status-item">
                        <span>서버 설정</span>
                        <strong>{webPushConfigured ? '활성' : '비활성'}</strong>
                      </div>
                      <div className="shared-app-settings-page__status-item">
                        <span>기기 등록</span>
                        <strong>{webPushRegistered ? '완료' : '미등록'}</strong>
                      </div>
                      <div className="shared-app-settings-page__status-item">
                        <span>기기 ID</span>
                        <strong>{getSavedNotificationDeviceId() || '-'}</strong>
                      </div>
                    </div>
                    <div className="shared-app-settings-page__inline-actions">
                      <Button onClick={() => void syncWebPushStatus()} disabled={webPushLoading}>
                        상태 확인
                      </Button>
                      <Button onClick={() => void handleRequestNotificationPermission()} disabled={webPushLoading}>
                        권한 요청
                      </Button>
                      <Button type="primary" onClick={() => void handleRegisterWebPush()} loading={webPushLoading}>
                        등록 실행
                      </Button>
                      <Button danger onClick={() => void handleUnregisterWebPush()} disabled={webPushLoading || !webPushRegistered}>
                        등록 해제
                      </Button>
                    </div>
                  </div>

                  {isAppleMobileDevice() && !isStandaloneDisplayMode() ? (
                    <Alert
                      showIcon
                      type="info"
                      message="아이폰/아이패드는 홈 화면에 추가한 PWA에서만 웹 푸시를 등록할 수 있습니다."
                    />
                  ) : null}
                </div>
              ),
            },
            {
              key: 'storage',
              label: '저장값',
              children: (
                <div className="shared-app-settings-page__panel">
                  <Tabs
                    activeKey={selectedStorageGroup?.key}
                    onChange={setStorageGroupKey}
                    className="shared-app-settings-page__subtabs"
                    items={STORAGE_GROUPS.map((group) => ({
                      key: group.key,
                      label: group.label,
                      children: (
                        <div className="shared-app-settings-page__section">
                          <div className="shared-app-settings-page__section-head">
                            <Text strong>{group.label}</Text>
                            <Text type="secondary">{group.description}</Text>
                          </div>
                          {storageFeedback ? <Alert showIcon type="info" message={storageFeedback} /> : null}
                          <div className="shared-app-settings-page__storage-list">
                            {group.entries.map((entry) => {
                              const storageItemKey = buildStorageItemKey(entry);

                              return (
                                <div key={storageItemKey} className="shared-app-settings-page__storage-item">
                                  <div className="shared-app-settings-page__storage-head">
                                    <div>
                                      <Text strong>{entry.label}</Text>
                                      <Text type="secondary">{entry.description}</Text>
                                    </div>
                                    <Text type="secondary">{entry.scope === 'local' ? 'localStorage' : 'sessionStorage'}</Text>
                                  </div>
                                  <Text code>{entry.storageKey}</Text>
                                  <Input.TextArea
                                    rows={4}
                                    value={storageDrafts[storageItemKey] ?? ''}
                                    placeholder="값이 없으면 비워 둡니다."
                                    onChange={(event) => {
                                      handleStorageDraftChange(entry, event.target.value);
                                    }}
                                  />
                                  <div className="shared-app-settings-page__inline-actions">
                                    <Button size="small" onClick={() => handleReloadStorageEntry(entry)}>
                                      다시 읽기
                                    </Button>
                                    <Button size="small" type="primary" onClick={() => handleSaveStorageEntry(entry)}>
                                      저장
                                    </Button>
                                    <Button size="small" danger onClick={() => handleClearStorageEntry(entry)}>
                                      비우기
                                    </Button>
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        </div>
                      ),
                    }))}
                  />
                </div>
              ),
            },
          ]}
        />
      </Form>
    </div>
  );
}
