import {
  ErrorBoundary,
  Suspense,
  type ErrorBoundaryFallbackProps,
} from '@suspensive/react';
import {
  QueryErrorResetBoundary,
  useSuspenseQuery,
} from '@tanstack/react-query';
import {
  createColumnHelper,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from '@tanstack/react-table';
import { useQueryState } from 'nuqs';

import { postQueries, type Post } from '@/entities/post';
import { Badge } from '@/shared/ui/badge';
import { Button } from '@/shared/ui/button';
import { Card, CardContent } from '@/shared/ui/card';
import { Input } from '@/shared/ui/input';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/shared/ui/select';

import { PostCharts } from './PostCharts';
import { PostListPanel } from './PostListPanel';
import { PostStatsCards } from './PostStatsCards';
import { PostTablePagination } from './PostTablePagination';
import { PostTableView } from './PostTableView';

import {
  filterByAuthor,
  PAGE_SIZE,
  postSearchParsers,
  toAuthorIds,
  toAvgBodyLength,
  toEmptyMessage,
  toNextSort,
  toSortingState,
} from '../model/postSearchParams';

const columnHelper = createColumnHelper<Post>();

const columns = [
  columnHelper.accessor('id', { header: 'ID' }),
  columnHelper.accessor('title', { header: '제목' }),
  columnHelper.accessor('userId', {
    header: '작성자',
    cell: (info) => (
      <Badge
        variant="outline"
        className="border-transparent bg-primary/10 text-primary"
      >
        작성자 {info.getValue()}
      </Badge>
    ),
  }),
  columnHelper.accessor('body', { header: '내용' }),
];

export const PostDashboard = () => {
  return (
    <QueryErrorResetBoundary>
      {({ reset }) => (
        <ErrorBoundary onReset={reset} fallback={DashboardErrorFallback}>
          <Suspense fallback={<DashboardSkeleton />}>
            <DashboardContent />
          </Suspense>
        </ErrorBoundary>
      )}
    </QueryErrorResetBoundary>
  );
};

const DashboardContent = () => {
  const { data: posts } = useSuspenseQuery(postQueries.list());

  const [keyword, setKeyword] = useQueryState('q', postSearchParsers.keyword);
  const [author, setAuthor] = useQueryState('author', postSearchParsers.author);
  const [sort, setSort] = useQueryState('sort', postSearchParsers.sort);
  const [page, setPage] = useQueryState('page', postSearchParsers.page);

  const authorPosts = filterByAuthor(posts, author);

  const table = useReactTable({
    data: authorPosts,
    columns,
    state: {
      globalFilter: keyword,
      sorting: toSortingState(sort),
      pagination: { pageIndex: page - 1, pageSize: PAGE_SIZE },
    },
    autoResetPageIndex: false,
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  });

  const handleKeywordChange = (value: string) => {
    setKeyword(value);
    setPage(1);
  };

  const handleAuthorChange = (value: string) => {
    // oxlint-disable-next-line unicorn/no-null -- '전체'는 URL에서 파라미터 제거(null)로 표현한다
    setAuthor(value === 'all' ? null : Number(value));
    setPage(1);
  };

  const cycleSort = () => {
    setSort(toNextSort(sort));
  };

  const authorIds = toAuthorIds(posts);
  const filteredPosts = table
    .getFilteredRowModel()
    .rows.map((row) => row.original);
  const filteredCount = filteredPosts.length;
  const avgLength = toAvgBodyLength(filteredPosts);
  const pageCount = table.getPageCount();
  const emptyMessage = toEmptyMessage(filteredCount);

  return (
    <div className="space-y-4">
      <PostStatsCards
        total={posts.length}
        filtered={filteredCount}
        authorCount={authorIds.length}
        avgLength={avgLength}
      />

      {filteredPosts.length > 0 && <PostCharts posts={filteredPosts} />}

      <div className="grid items-start gap-4 xl:grid-cols-[minmax(0,1fr)_22rem]">
        <Card>
          <CardContent className="space-y-4">
            <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
              <Input
                value={keyword}
                onChange={(event) => handleKeywordChange(event.target.value)}
                placeholder="제목·내용으로 검색해요"
                aria-label="글 검색"
                className="sm:max-w-sm"
              />
              <Select
                value={author === null ? 'all' : String(author)}
                onValueChange={handleAuthorChange}
              >
                <SelectTrigger
                  className="w-full sm:w-44"
                  aria-label="작성자 필터"
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="all">작성자 전체</SelectItem>
                  {authorIds.map((id) => (
                    <SelectItem key={id} value={String(id)}>
                      작성자 {id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className="text-sm text-muted-foreground sm:ml-auto">
                총 {filteredCount.toLocaleString()}건
              </p>
            </div>

            <PostTableView
              table={table}
              sort={sort}
              onCycleSort={cycleSort}
              emptyMessage={emptyMessage}
            />

            {pageCount > 1 && (
              <PostTablePagination
                page={page}
                pageCount={pageCount}
                onPageChange={(next) => {
                  setPage(next);
                }}
              />
            )}
          </CardContent>
        </Card>

        <PostListPanel />
      </div>
    </div>
  );
};

const DashboardErrorFallback = ({
  error,
  reset,
}: ErrorBoundaryFallbackProps) => {
  return (
    <div className="space-y-3 rounded-xl border p-6 text-center">
      <p className="text-sm font-medium">대시보드를 불러오지 못했어요.</p>
      <p className="text-sm text-muted-foreground">{error.message}</p>
      <Button variant="outline" onClick={reset}>
        다시 시도
      </Button>
    </div>
  );
};

const DashboardSkeleton = () => {
  return (
    <div className="space-y-4" aria-busy="true">
      <div className="grid gap-3 sm:grid-cols-3">
        {Array.from({ length: 3 }, (_, index) => (
          <div key={index} className="h-24 animate-pulse rounded-xl bg-muted" />
        ))}
      </div>
      <div className="grid gap-3 lg:grid-cols-2">
        {Array.from({ length: 2 }, (_, index) => (
          <div key={index} className="h-72 animate-pulse rounded-xl bg-muted" />
        ))}
      </div>
      {Array.from({ length: 5 }, (_, index) => (
        <div key={index} className="h-10 animate-pulse rounded-lg bg-muted" />
      ))}
    </div>
  );
};
