"use client";

/**
 * Notifications 页面 - 通知中心（P5）
 *
 * 数据来源（02-architecture-design.md §5）：
 * - GraphQL MyNotificationsQuery（持久化通知列表）
 * - WebSocket 实时推送（push-gateway，use-notifications-websocket hook）
 *
 * 功能：
 * - 通知列表（标题、消息、时间、类型）
 * - 未读高亮 + 标记已读（乐观更新） + 全部标记已读
 * - 类型筛选（全部 / 未读 / 作业 / 考试 / 广播）
 * - WebSocket 连接状态指示器
 *
 * 维护者：ai13（teacher-portal）
 */

import { useMemo, useState } from "react";
import { useMutation, useQuery } from "urql";
import { Loading, Empty } from "@edu/ui-components";
import {
  MyNotificationsQuery,
  MarkAsReadMutation,
} from "@/lib/graphql-p5";
import type { NotificationItem, NotificationType } from "@/lib/graphql-p5";
import {
  useNotificationsWebSocket,
  NOTIFICATION_TYPE_LABEL,
} from "@/hooks/use-notifications-websocket";
import type { ConnectionState } from "@/hooks/use-notifications-websocket";

type FilterKey = "all" | "unread" | NotificationType;

const FILTERS: { key: FilterKey; label: string }[] = [
  { key: "all", label: "全部" },
  { key: "unread", label: "未读" },
  { key: "HOMEWORK_SUBMITTED", label: "作业" },
  { key: "EXAM_GRADED", label: "考试" },
  { key: "BROADCAST", label: "广播" },
];

function relativeTime(iso: string): string {
  const diff = Date.now() - new Date(iso).getTime();
  const minute = 60 * 1000;
  const hour = 60 * minute;
  const day = 24 * hour;
  if (diff < minute) return "刚刚";
  if (diff < hour) return `${Math.floor(diff / minute)} 分钟前`;
  if (diff < day) return `${Math.floor(diff / hour)} 小时前`;
  if (diff < 7 * day) return `${Math.floor(diff / day)} 天前`;
  return new Date(iso).toLocaleDateString("zh-CN");
}

function ConnectionDot({ state }: { state: ConnectionState }): JSX.Element {
  const map: Record<ConnectionState, { dot: string; text: string }> = {
    connected: { dot: "bg-success", text: "已连接" },
    connecting: { dot: "bg-warning", text: "连接中" },
    disconnected: { dot: "bg-danger", text: "已断开" },
    error: { dot: "bg-danger", text: "连接错误" },
  };
  const cfg = map[state];
  return (
    <span className="inline-flex items-center gap-2">
      <span className={`inline-block w-2 h-2 rounded-full ${cfg.dot}`} />
      <span className="text-tiny text-ink-muted">{cfg.text}</span>
    </span>
  );
}

export default function NotificationsPage(): JSX.Element {
  const [filter, setFilter] = useState<FilterKey>("all");
  const [readOverrides, setReadOverrides] = useState<Record<string, boolean>>(
    {},
  );

  const [result] = useQuery({ query: MyNotificationsQuery });
  const [, markAsRead] = useMutation(MarkAsReadMutation);
  const { notifications: wsNotifications, connectionState, reconnect } =
    useNotificationsWebSocket();

  // 合并：WebSocket 实时推送（新） + GraphQL 持久化列表，按 id 去重
  const merged = useMemo<NotificationItem[]>(() => {
    const persisted = (result.data?.myNotifications as NotificationItem[] | undefined) ?? [];
    const seen = new Set<string>();
    const list: NotificationItem[] = [];
    for (const n of [...wsNotifications, ...persisted]) {
      if (seen.has(n.id)) continue;
      seen.add(n.id);
      list.push(n);
    }
    return list;
  }, [result.data, wsNotifications]);

  const displayed = useMemo<NotificationItem[]>(() => {
    const withOverrides = merged.map((n) => ({
      ...n,
      read: readOverrides[n.id] ?? n.read,
    }));
    switch (filter) {
      case "all":
        return withOverrides;
      case "unread":
        return withOverrides.filter((n) => !n.read);
      default:
        return withOverrides.filter((n) => n.type === filter);
    }
  }, [merged, readOverrides, filter]);

  const unreadCount = useMemo(
    () =>
      merged.filter((n) => !(readOverrides[n.id] ?? n.read)).length,
    [merged, readOverrides],
  );

  const handleMarkAsRead = (id: string): void => {
    setReadOverrides((prev) => ({ ...prev, [id]: true }));
    void markAsRead({ id }).then((res) => {
      if (res.error) {
        setReadOverrides((prev) => ({ ...prev, [id]: false }));
      }
    });
  };

  const handleMarkAllRead = (): void => {
    const unreadIds = merged
      .filter((n) => !(readOverrides[n.id] ?? n.read))
      .map((n) => n.id);
    if (unreadIds.length === 0) return;
    const overrides: Record<string, boolean> = {};
    for (const id of unreadIds) overrides[id] = true;
    setReadOverrides((prev) => ({ ...prev, ...overrides }));
    for (const id of unreadIds) {
      void markAsRead({ id }).then((res) => {
        if (res.error) {
          setReadOverrides((prev) => ({ ...prev, [id]: false }));
        }
      });
    }
  };

  return (
    <div className="px-10 py-10">
      <header className="mb-8">
        <div className="flex items-baseline justify-between">
          <div>
            <h1 className="text-3xl font-serif text-ink">通知中心</h1>
            <p className="mt-1 text-sm text-ink-muted">
              GraphQL MyNotificationsQuery + WebSocket 实时推送（P5）
            </p>
          </div>
          <ConnectionDot state={connectionState} />
        </div>
      </header>

      <div className="rule-thin mb-8" />

      {/* 筛选 + 操作 */}
      <section className="mb-6 flex items-center justify-between flex-wrap gap-4">
        <div className="flex items-center gap-2 flex-wrap">
          {FILTERS.map((f) => (
            <button
              key={f.key}
              type="button"
              onClick={() => setFilter(f.key)}
              className={`px-3 py-1 text-tiny uppercase tracking-wide rounded-button border transition-colors ${
                filter === f.key
                  ? "bg-accent text-ink-on-accent border-accent"
                  : "bg-transparent text-ink-muted border-rule hover:text-ink"
              }`}
            >
              {f.label}
            </button>
          ))}
        </div>
        <div className="flex items-center gap-4">
          <span className="text-tiny text-ink-muted">
            未读 <span className="font-mono text-accent">{unreadCount}</span>
          </span>
          <button
            type="button"
            onClick={handleMarkAllRead}
            disabled={unreadCount === 0}
            className="text-tiny uppercase tracking-wide text-accent hover:opacity-70 disabled:opacity-40 disabled:cursor-not-allowed"
          >
            全部标记已读
          </button>
          <button
            type="button"
            onClick={reconnect}
            className="text-tiny uppercase tracking-wide text-ink-muted hover:opacity-70"
          >
            重连
          </button>
        </div>
      </section>

      {result.fetching && merged.length === 0 ? (
        <Loading lines={5} />
      ) : result.error && merged.length === 0 ? (
        <div className="mark-left mb-4 py-2 border-l-2 border-danger pl-md">
          <p className="text-sm px-3 text-danger">
            加载失败：{result.error.message}
          </p>
        </div>
      ) : displayed.length === 0 ? (
        <Empty title="暂无通知" description="当前筛选条件下没有通知" />
      ) : (
        <ul className="space-y-0">
          {displayed.map((n) => {
            const isRead = readOverrides[n.id] ?? n.read;
            return (
              <li
                key={n.id}
                className={`py-4 grid grid-cols-12 gap-4 items-baseline border-b border-rule ${
                  isRead ? "" : "bg-subtle"
                }`}
              >
                <div className="col-span-9 pl-2">
                  <div className="flex items-center gap-3 mb-1">
                    <span className="text-tiny uppercase tracking-wide px-2 py-0.5 rounded-button border border-rule text-ink-muted">
                      {NOTIFICATION_TYPE_LABEL[n.type]}
                    </span>
                    <h3
                      className={`text-lg font-serif ${
                        isRead ? "text-ink-muted" : "text-ink"
                      }`}
                    >
                      {n.title}
                    </h3>
                    {!isRead && (
                      <span className="inline-block w-2 h-2 rounded-full bg-accent" />
                    )}
                  </div>
                  <p className="text-sm text-ink-muted">{n.message}</p>
                  <p className="mt-1 text-tiny font-mono text-ink-subtle">
                    {relativeTime(n.createdAt)}
                  </p>
                </div>
                <div className="col-span-3 text-right">
                  {isRead ? (
                    <span className="text-tiny text-ink-subtle">已读</span>
                  ) : (
                    <button
                      type="button"
                      onClick={() => handleMarkAsRead(n.id)}
                      className="text-tiny uppercase tracking-wide text-accent hover:opacity-70"
                    >
                      标记已读
                    </button>
                  )}
                </div>
              </li>
            );
          })}
        </ul>
      )}
    </div>
  );
}
