import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import {
  Button,
  Card,
  Descriptions,
  Form,
  Grid,
  Input,
  Radio,
  Select,
  Spin,
  Tag,
  Typography,
} from "antd";
import { DoubleLeftOutlined, DoubleRightOutlined } from "@ant-design/icons";
import { api } from "../api";
import {
  attemptDetailPath,
  readScoreListQuery,
  type ScoreListQuery,
} from "../scoreQueryParams";
import "./AttemptReviewPage.css";

const { useBreakpoint } = Grid;
const { Title } = Typography;

const SECTION_CN = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];

type Q = {
  external_item_id: string;
  part_name?: string;
  item_order?: number;
  title_html?: string;
  detail_json?: string;
  reference_answer?: string;
  user_answer?: string;
  is_correct: boolean;
  score: number;
  max_score?: number;
};

type Part = { part_name: string; questions: Q[] };

type Attempt = {
  attempt_id: string;
  examinee_name?: string;
  id_card?: string;
  score: number;
  paper_total_score?: number;
  paper_name?: string;
  subject_name?: string;
  room_id?: string;
  seat_number?: string;
  start_time?: string;
  end_time?: string;
  parts: Part[];
};

type FlatItem = { key: string; part: string; q: Q; idx: number; displayNo: number };

function stripHtml(html: string) {
  return html.replace(/<[^>]+>/g, "");
}

function isAnswered(q: Q) {
  return Boolean(q.user_answer?.trim());
}

function bubbleClassName(q: Q, isActive: boolean) {
  const classes = ["attempt-review__bubble"];
  if (!isAnswered(q)) classes.push("attempt-review__bubble--unanswered");
  else if (q.is_correct) classes.push("attempt-review__bubble--correct");
  else classes.push("attempt-review__bubble--wrong");
  if (isActive) classes.push("attempt-review__bubble--active");
  return classes.join(" ");
}

function parseOptions(detail_json?: string) {
  if (!detail_json) return [];
  try {
    const o = JSON.parse(detail_json);
    const qs = o?.item?.questions;
    if (Array.isArray(qs) && qs[0]?.options) return qs[0].options;
  } catch {
    /* ignore */
  }
  return [];
}

function scrollChildIntoContainer(container: HTMLElement, el: HTMLElement) {
  const cRect = container.getBoundingClientRect();
  const eRect = el.getBoundingClientRect();
  const nextTop = container.scrollTop + (eRect.top - cRect.top);
  container.scrollTo({ top: Math.max(0, nextTop), behavior: "auto" });
}

function QuestionIndex({
  parts,
  flat,
  activeKey,
  collapsed,
  onToggleCollapse,
  onJump,
  mobile,
}: {
  parts: Part[];
  flat: FlatItem[];
  activeKey: string | null;
  collapsed: boolean;
  onToggleCollapse: () => void;
  onJump: (key: string) => void;
  mobile?: boolean;
}) {
  const [typeFilter, setTypeFilter] = useState<string>("all");

  const visibleParts = useMemo(() => {
    if (typeFilter === "all") return parts;
    return parts.filter((p) => p.part_name === typeFilter);
  }, [parts, typeFilter]);

  return (
    <aside
      className={[
        "attempt-review__index",
        mobile ? "attempt-review__index-mobile" : "",
        collapsed && !mobile ? "attempt-review__index--collapsed" : "",
      ]
        .filter(Boolean)
        .join(" ")}
      aria-label="题目索引"
    >
      <div className="attempt-review__index-toolbar">
        {!mobile && (
          <Button
            type="text"
            size="small"
            icon={collapsed ? <DoubleRightOutlined /> : <DoubleLeftOutlined />}
            onClick={onToggleCollapse}
            aria-label={collapsed ? "展开索引侧栏" : "收起索引侧栏"}
          />
        )}
      </div>
      {collapsed && !mobile ? (
        <div className="attempt-review__index-collapsed-label">题目索引</div>
      ) : (
        <>
          <div className="attempt-review__index-filter">
            <Radio.Group
              size="small"
              value={typeFilter}
              onChange={(e) => setTypeFilter(e.target.value)}
              className="attempt-review__type-radio"
            >
              <Radio.Button value="all">全部</Radio.Button>
              {parts.map((p) => (
                <Radio.Button key={p.part_name} value={p.part_name}>{p.part_name}</Radio.Button>
              ))}
            </Radio.Group>
          </div>
          <div className="attempt-review__index-body">
            {visibleParts.map((p) => {
              const sectionIdx = parts.findIndex((x) => x.part_name === p.part_name);
              const sectionLabel = `${SECTION_CN[sectionIdx] ?? sectionIdx + 1}、${p.part_name}`;
              return (
                <section key={p.part_name} className="attempt-review__section">
                  <h4 className="attempt-review__section-title">{sectionLabel}</h4>
                  <div className="attempt-review__bubble-grid">
                    {p.questions.map((q, i) => {
                      const meta = flat.find((f) => f.key === q.external_item_id);
                      const no = meta?.displayNo ?? (q.item_order ?? i) + 1;
                      const isActive = activeKey === q.external_item_id;
                      return (
                        <button
                          key={q.external_item_id}
                          type="button"
                          className={bubbleClassName(q, isActive)}
                          onClick={() => onJump(q.external_item_id)}
                          title={`第 ${no} 题`}
                        >
                          {no}
                        </button>
                      );
                    })}
                  </div>
                </section>
              );
            })}
          </div>
        </>
      )}
    </aside>
  );
}

type ScoreListItem = {
  attempt_id: string;
  name: string;
  id_card: string;
  subject_name: string;
  score: number;
};

const ROSTER_PAGE_SIZE = 40;

function AttemptRoster({
  activeAttemptId,
  listQuery,
  onSelectAttempt,
  onSearch,
}: {
  activeAttemptId: string;
  listQuery: ScoreListQuery;
  onSelectAttempt: (attemptId: string) => void;
  onSearch: (filters: Pick<ScoreListQuery, "name" | "id_card" | "subject">) => void;
}) {
  const [form] = Form.useForm<Pick<ScoreListQuery, "name" | "id_card" | "subject">>();
  const [subjects, setSubjects] = useState<string[]>([]);
  const [items, setItems] = useState<ScoreListItem[]>([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [filters, setFilters] = useState({
    name: listQuery.name,
    id_card: listQuery.id_card,
    subject: listQuery.subject,
  });
  const scrollRef = useRef<HTMLDivElement>(null);
  const loadingMoreRef = useRef(false);

  useEffect(() => {
    form.setFieldsValue({
      name: listQuery.name,
      id_card: listQuery.id_card,
      subject: listQuery.subject,
    });
    setFilters({
      name: listQuery.name,
      id_card: listQuery.id_card,
      subject: listQuery.subject,
    });
  }, [listQuery.name, listQuery.id_card, listQuery.subject, form]);

  useEffect(() => {
    api.get<{ items: string[] }>("/api/subjects").then((r) => setSubjects(r.data.items));
  }, []);

  const fetchPage = useCallback(
    async (p: number, append: boolean) => {
      if (loadingMoreRef.current) return;
      loadingMoreRef.current = true;
      setLoading(true);
      try {
        const { data: res } = await api.get("/api/scores", {
          params: { page: p, page_size: ROSTER_PAGE_SIZE, ...filters },
        });
        setTotal(res.total);
        setPage(p);
        setItems((prev) => (append ? [...prev, ...res.items] : res.items));
      } finally {
        setLoading(false);
        loadingMoreRef.current = false;
      }
    },
    [filters]
  );

  useEffect(() => {
    fetchPage(1, false);
  }, [fetchPage]);

  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => {
      if (loadingMoreRef.current || loading) return;
      if (items.length >= total) return;
      if (el.scrollHeight - el.scrollTop - el.clientHeight < 64) {
        fetchPage(page + 1, true);
      }
    };
    el.addEventListener("scroll", onScroll);
    return () => el.removeEventListener("scroll", onScroll);
  }, [loading, items.length, total, page, fetchPage]);

  useEffect(() => {
    const el = scrollRef.current?.querySelector<HTMLElement>(
      `[data-attempt-id="${activeAttemptId}"]`
    );
    el?.scrollIntoView({ block: "nearest" });
  }, [activeAttemptId, items]);

  return (
    <aside className="attempt-review__roster">
      <Form
        form={form}
        className="attempt-review__roster-form"
        size="small"
        layout="horizontal"
        labelAlign="right"
        colon={false}
        labelCol={{ flex: "0 0 52px" }}
        wrapperCol={{ flex: "1 1 0" }}
        onFinish={(v) => onSearch(v)}
      >
        <Form.Item name="name" label="姓名" className="attempt-review__roster-field">
          <Input allowClear />
        </Form.Item>
        <Form.Item name="id_card" label="证件号" className="attempt-review__roster-field">
          <Input allowClear />
        </Form.Item>
        <Form.Item name="subject" label="科目" className="attempt-review__roster-field">
          <Select allowClear options={subjects.map((s) => ({ value: s, label: s }))} />
        </Form.Item>
        <Button type="primary" htmlType="submit" block size="small">查询</Button>
      </Form>
      <div ref={scrollRef} className="attempt-review__roster-list">
        {items.map((row) => (
          <button
            key={row.attempt_id}
            type="button"
            data-attempt-id={row.attempt_id}
            className={[
              "attempt-review__roster-item",
              row.attempt_id === activeAttemptId ? "attempt-review__roster-item--active" : "",
            ]
              .filter(Boolean)
              .join(" ")}
            onClick={() => {
              if (row.attempt_id !== activeAttemptId) onSelectAttempt(row.attempt_id);
            }}
          >
            <div className="attempt-review__roster-name">{row.name || "—"}</div>
            <div className="attempt-review__roster-meta">
              <span>{row.score} 分</span>
              <span className="attempt-review__roster-subject">{row.subject_name}</span>
            </div>
          </button>
        ))}
        {loading && <div className="attempt-review__roster-hint">加载中…</div>}
        {!loading && items.length === 0 && <div className="attempt-review__roster-hint">无数据</div>}
        {!loading && items.length > 0 && items.length >= total && (
          <div className="attempt-review__roster-hint">已加载全部</div>
        )}
      </div>
    </aside>
  );
}

export default function AttemptReviewPage() {
  const { attemptId } = useParams();
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const listQuery = readScoreListQuery(searchParams);
  const [data, setData] = useState<Attempt | null>(null);
  const [detailLoading, setDetailLoading] = useState(true);
  const [activeKey, setActiveKey] = useState<string | null>(null);
  const [indexCollapsed, setIndexCollapsed] = useState(false);
  const detailScrollRef = useRef<HTMLDivElement>(null);
  const screens = useBreakpoint();
  const isWide = !!screens.lg;

  const selectAttempt = useCallback(
    (id: string) => {
      if (id === attemptId) return;
      navigate(attemptDetailPath(id, listQuery), { replace: true, preventScrollReset: true });
    },
    [attemptId, listQuery, navigate]
  );

  const searchRoster = useCallback(
    (filters: Pick<ScoreListQuery, "name" | "id_card" | "subject">) => {
      navigate(
        attemptDetailPath(attemptId!, { ...filters, page: 1, page_size: listQuery.page_size }),
        { replace: true, preventScrollReset: true }
      );
    },
    [attemptId, listQuery.page_size, navigate]
  );

  useEffect(() => {
    if (!attemptId) return;
    let cancelled = false;
    setDetailLoading(true);
    setActiveKey(null);
    detailScrollRef.current?.scrollTo(0, 0);
    api
      .get<Attempt>(`/api/attempts/${attemptId}`)
      .then((r) => {
        if (!cancelled) setData(r.data);
      })
      .finally(() => {
        if (!cancelled) setDetailLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [attemptId]);

  useEffect(() => {
    const prev = document.documentElement.style.overflow;
    const prevBody = document.body.style.overflow;
    document.documentElement.style.overflow = "hidden";
    document.body.style.overflow = "hidden";
    return () => {
      document.documentElement.style.overflow = prev;
      document.body.style.overflow = prevBody;
    };
  }, []);

  const flat = useMemo(() => {
    if (!data) return [];
    const out: FlatItem[] = [];
    let n = 0;
    data.parts.forEach((p) =>
      p.questions.forEach((q, i) => {
        n += 1;
        out.push({
          key: q.external_item_id,
          part: p.part_name,
          q,
          idx: n,
          displayNo: (q.item_order ?? i) + 1,
        });
      })
    );
    return out;
  }, [data]);

  const jumpTo = useCallback((key: string) => {
    setActiveKey(key);
    requestAnimationFrame(() => {
      const container = detailScrollRef.current;
      const el = document.getElementById(`q-${key}`);
      if (!container || !el) return;
      scrollChildIntoContainer(container, el);
    });
  }, []);

  useEffect(() => {
    const container = detailScrollRef.current;
    if (!container || !data || flat.length === 0) return;

    const observer = new IntersectionObserver(
      (entries) => {
        const visible = entries
          .filter((e) => e.isIntersecting)
          .sort((a, b) => (a.boundingClientRect.top ?? 0) - (b.boundingClientRect.top ?? 0));
        const id = visible[0]?.target.id;
        if (id?.startsWith("q-")) {
          setActiveKey(id.replace("q-", ""));
        }
      },
      { root: container, rootMargin: "-8px 0px -70% 0px", threshold: 0 }
    );

    flat.forEach(({ key }) => {
      const el = document.getElementById(`q-${key}`);
      if (el) observer.observe(el);
    });
    return () => observer.disconnect();
  }, [data, flat]);

  const indexPanel = data ? (
    <QuestionIndex
      parts={data.parts}
      flat={flat}
      activeKey={activeKey}
      collapsed={indexCollapsed}
      onToggleCollapse={() => setIndexCollapsed((c) => !c)}
      onJump={jumpTo}
      mobile={!isWide}
    />
  ) : null;

  const detailPanel = data ? (
    <div className="attempt-review__detail-inner">
      {data.parts.map((p) => (
        <div key={p.part_name} id={`part-${p.part_name}`}>
          <Title level={5}>{p.part_name}</Title>
          {p.questions.map((q, i) => (
            <div key={q.external_item_id} id={`q-${q.external_item_id}`} className="attempt-review__question-anchor">
              <Card
                size="small"
                className="attempt-review__question-card"
                title={`第 ${(q.item_order ?? i) + 1} 题`}
              >
                <div dangerouslySetInnerHTML={{ __html: q.title_html || "" }} />
                <ul>
                  {parseOptions(q.detail_json).map((opt: { identifier: string; text: string }) => (
                    <li key={opt.identifier}>{opt.identifier}. {stripHtml(opt.text)}</li>
                  ))}
                </ul>
                <p>考生答案：{q.user_answer || "—"}</p>
                <p>参考答案：{q.reference_answer || "—"}</p>
                <p>
                  {q.is_correct ? <Tag color="success">正确</Tag> : <Tag color="error">错误</Tag>}
                  得分：{q.score} / {q.max_score ?? 1}
                </p>
              </Card>
            </div>
          ))}
        </div>
      ))}
    </div>
  ) : null;

  const centerRight = (
    <div className="attempt-review__center-right">
      {detailLoading && (
        <div className="attempt-review__center-right-mask" aria-busy="true">
          <Spin />
        </div>
      )}
      {data && (
        <Card className="attempt-review__header" size="small">
          <Descriptions column={{ xs: 1, sm: 2 }} size="small">
            <Descriptions.Item label="姓名">{data.examinee_name}</Descriptions.Item>
            <Descriptions.Item label="证件号">{data.id_card}</Descriptions.Item>
            <Descriptions.Item label="科目">{data.subject_name}</Descriptions.Item>
            <Descriptions.Item label="总分">{data.score} / {data.paper_total_score}</Descriptions.Item>
            <Descriptions.Item label="试卷">{data.paper_name}</Descriptions.Item>
            <Descriptions.Item label="考场">{data.room_id} {data.seat_number}</Descriptions.Item>
            <Descriptions.Item label="考试时间">{data.start_time} ~ {data.end_time}</Descriptions.Item>
          </Descriptions>
        </Card>
      )}
      {!data && detailLoading && (
        <div className="attempt-review__center-right-placeholder">
          <Spin />
        </div>
      )}
      {data && (
        <>
          {!isWide && <div className="attempt-review__index-mobile-wrap">{indexPanel}</div>}
          <div className="attempt-review__index-detail-row">
            {isWide && (
              <div
                className={
                  indexCollapsed
                    ? "attempt-review__index-col attempt-review__index-col--collapsed"
                    : "attempt-review__index-col"
                }
              >
                {indexPanel}
              </div>
            )}
            <div ref={detailScrollRef} className="attempt-review__detail">
              {detailPanel}
            </div>
          </div>
        </>
      )}
    </div>
  );

  return (
    <div className="attempt-review">
      <div className="attempt-review__split">
        {isWide && attemptId && (
          <div className="attempt-review__roster-col">
            <AttemptRoster
              activeAttemptId={attemptId}
              listQuery={listQuery}
              onSelectAttempt={selectAttempt}
              onSearch={searchRoster}
            />
          </div>
        )}
        {centerRight}
      </div>
    </div>
  );
}
