import { useState } from "react";
import { useProjectContext } from "@/sdk";
import { toast } from "sonner";
import { useThreadManager, useThreads } from "@/components/chat/store/hooks";
import type { Task } from "@/components/chat/task/types";
import { useStudioTools } from "@/lib/studio-tools";
import type { StudioToolInput as ToolInput } from "@decocms/shared/tools/tool-io";
import {
  groupShowMoreIdentity,
  shouldDeferGroupProbe,
} from "./group-show-more-identity";
import { enqueueGroupThreadsFetch } from "./group-threads-fetch-queue";
import {
  cacheGroupProbeResult,
  ensureGroupProbe,
  getCachedGroupProbeResult,
  inferServerHasMoreWithoutProbe,
} from "./group-threads-probe-cache";
import {
  buildShowMoreArgs,
  deriveGroupHasMore,
  groupHasMoreFromTotal,
  GROUP_PAGE_SIZE,
  nextPageOffset,
  resolveGroupHasMore,
  threadMatchesSidebarGroup,
  type GroupKind,
  type SidebarFilters,
} from "./next-page-offset";

interface ShowMoreState {
  isFetching: boolean;
  isProbing: boolean;
  identity: string;
  /** Tracks which identity already subscribed to `ensureGroupProbe`. */
  probeSubscribedIdentity: string | null;
  /** Authoritative once probed or after a per-group page fetch. */
  serverHasMore: boolean | null;
}

function parseListResult(result: unknown): {
  items: Task[];
  hasMore: boolean;
  totalCount?: number;
} {
  const payload = result as {
    items?: Task[];
    hasMore?: boolean;
    totalCount?: number;
  };
  const items = Array.isArray(payload.items) ? payload.items : [];
  return {
    items,
    hasMore: payload.hasMore ?? items.length === GROUP_PAGE_SIZE,
    totalCount:
      typeof payload.totalCount === "number" ? payload.totalCount : undefined,
  };
}

function countNewMatchingItems(
  items: Task[],
  loadedIds: Set<string>,
  kind: GroupKind,
  key: string,
  filters: SidebarFilters,
): number {
  let count = 0;
  for (const item of items) {
    if (loadedIds.has(item.id)) continue;
    if (!threadMatchesSidebarGroup(item, kind, key, filters)) continue;
    count++;
  }
  return count;
}

/**
 * Per-group "Show more" controller. Owns `hasMore`/`isFetching` for one
 * (org, kind, key, filters) tuple.
 */
export function useGroupShowMore(
  kind: GroupKind,
  key: string,
  filters: SidebarFilters,
  /** Precomputed visible count for this group (avoids O(T) scan per hook). */
  visibleCount?: number,
) {
  const { org } = useProjectContext();
  const identity = groupShowMoreIdentity(org.id, kind, key, filters);
  const deferProbe = shouldDeferGroupProbe(filters);
  const [state, setState] = useState<ShowMoreState>({
    isFetching: false,
    isProbing: false,
    identity,
    probeSubscribedIdentity: null,
    serverHasMore: null,
  });

  if (state.identity !== identity) {
    setState({
      isFetching: false,
      isProbing: false,
      identity,
      probeSubscribedIdentity: null,
      serverHasMore: null,
    });
  }

  const manager = useThreadManager();
  const { threads, hasMore: globalHasMore } = useThreads();
  const resolvedVisibleCount =
    visibleCount ?? nextPageOffset(threads, kind, key, filters);
  const derivedHasMore = deriveGroupHasMore(
    resolvedVisibleCount,
    globalHasMore,
  );
  const inferredServerHasMore = inferServerHasMoreWithoutProbe(
    resolvedVisibleCount,
    globalHasMore,
  );

  const studio = useStudioTools();

  if (state.identity === identity && state.serverHasMore === null) {
    const resolvedWithoutProbe =
      inferredServerHasMore ?? getCachedGroupProbeResult(identity);
    if (resolvedWithoutProbe !== undefined) {
      setState({
        isFetching: false,
        isProbing: false,
        identity,
        probeSubscribedIdentity: state.probeSubscribedIdentity,
        serverHasMore: resolvedWithoutProbe,
      });
    }
  }

  const needsNetworkProbe =
    !deferProbe &&
    state.identity === identity &&
    state.serverHasMore === null &&
    inferredServerHasMore === null &&
    getCachedGroupProbeResult(identity) === undefined;

  if (needsNetworkProbe && state.probeSubscribedIdentity !== identity) {
    setState((s) =>
      s.identity === identity
        ? { ...s, isProbing: true, probeSubscribedIdentity: identity }
        : s,
    );
    ensureGroupProbe(
      identity,
      async () => {
        const args = buildShowMoreArgs(kind, key, 0, filters, 1);
        const result = await studio.call(
          "COLLECTION_THREADS_LIST",
          args as unknown as ToolInput<"COLLECTION_THREADS_LIST">,
        );
        const { totalCount } = parseListResult(result);
        const visible = nextPageOffset(
          manager.threads.get(),
          kind,
          key,
          filters,
        );
        if (totalCount === undefined) return false;
        return groupHasMoreFromTotal(visible, totalCount);
      },
      (serverHasMore) => {
        setState((s) => {
          if (s.identity !== identity) return s;
          return {
            ...s,
            isProbing: false,
            serverHasMore,
          };
        });
      },
    );
  }

  async function loadMore(): Promise<void> {
    if (state.isFetching || state.isProbing || deferProbe) return;
    const capturedIdentity = identity;
    setState((s) =>
      s.identity === capturedIdentity ? { ...s, isFetching: true } : s,
    );
    try {
      const offset = nextPageOffset(threads, kind, key, filters);
      const args = buildShowMoreArgs(
        kind,
        key,
        offset,
        filters,
        GROUP_PAGE_SIZE,
      );

      const result = await enqueueGroupThreadsFetch(() =>
        studio.call(
          "COLLECTION_THREADS_LIST",
          args as unknown as ToolInput<"COLLECTION_THREADS_LIST">,
        ),
      );

      const {
        items,
        hasMore: nextHasMore,
        totalCount,
      } = parseListResult(result);

      const loadedIds = new Set(threads.map((t) => t.id));
      const newMatchingCount = countNewMatchingItems(
        items,
        loadedIds,
        kind,
        key,
        filters,
      );
      const knownBefore = offset;

      manager.mergeThreads(items);
      setState((s) => {
        if (s.identity !== capturedIdentity) return s;
        let serverHasMore: boolean;
        if (totalCount !== undefined) {
          serverHasMore = groupHasMoreFromTotal(
            knownBefore + newMatchingCount,
            totalCount,
          );
        } else {
          serverHasMore =
            items.length === 0 || newMatchingCount === 0 ? false : nextHasMore;
        }
        cacheGroupProbeResult(capturedIdentity, serverHasMore);
        return {
          ...s,
          isFetching: false,
          serverHasMore,
        };
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      toast.error(`Could not load more tasks: ${msg}`);
      setState((s) =>
        s.identity === capturedIdentity ? { ...s, isFetching: false } : s,
      );
    }
  }

  const isBusy = state.isFetching || state.isProbing;
  const showButton =
    !deferProbe &&
    !isBusy &&
    resolveGroupHasMore(derivedHasMore, state.serverHasMore);

  return {
    hasMore: showButton,
    isFetching: isBusy,
    isProbing: state.isProbing,
    serverHasMore: state.serverHasMore,
    loadMore,
  };
}
