import { useEffect, useRef } from 'react';
import { useParams, useNavigate } from 'react-router';
import type { LayoutNode, SelectedSession } from 'officerdev';
import { WorkspaceView, chatListPath, cwdFromSplat, useSelectedChatSession } from 'officerdev';
import { toast } from '@/components/ui/sonner';
import { useIsMobile } from 'hooks/useIsMobile';
import { useClient } from 'hooks/useClient';
import { errorText } from 'helpers/error-text';
import { useDashboardState } from 'state/useDashboardState';
import type { ClaudeSessionDetail } from 'state/useClaudeSessions';
import { defaultLayout, hasAppType } from './defaultLayout';

// How many messages to render on first open (anchored to the bottom); scroll-up pages older ones in.
const CHAT_TAIL = 20;

type SessionListPageProps = {
  isNew?: boolean;
};

export const SessionListPage = ({ isNew }: SessionListPageProps) => {
  // The group is a path suffix now, not ?cwd= — see officerdev/apps/ChatHistory/chat-routes.ts.
  const { sessionId, '*': splat } = useParams<{ sessionId: string; '*': string }>();
  const groupCwd = cwdFromSplat(splat);
  const [selected, setSelected] = useSelectedChatSession();
  const client = useClient();
  const selectedRef = useRef(selected);
  selectedRef.current = selected;
  // A layout persisted before the chat panels were renamed still names `officerdev/chat`, which no
  // longer resolves; `appTypes` lands anything unknown on the detail panel.
  const workspace = useDashboardState<LayoutNode>('screens/chat', defaultLayout);
  const isMobile = useIsMobile();
  const navigate = useNavigate();
  const mobilePanelId = isMobile && (sessionId || isNew) ? 'chat-detail' : undefined;

  // Adopt a structural change to this screen's layout.
  //
  // `useDashboardState` seeds its default ONLY when the key is absent, so anyone who has ever opened
  // /chat keeps the shape it had then — for good. `appTypes`/`normalizeLayout` does not help: it repairs
  // which app a panel runs, never the tree, so adding the Live panel above the list would have been
  // invisible to every existing user and visible only on a fresh account.
  //
  // Replacing outright is safe *here* specifically because the screen is `locked`: its structure is
  // dictated by code and the only thing a user can have contributed is the column sizes, which is a
  // cheap thing to lose once. Terminates because the replacement contains the panel it tests for.
  useEffect(() => {
    if (!workspace.isLoaded) return;
    if (hasAppType(workspace.value, 'chat-live')) return;
    workspace.setValue(defaultLayout);
  }, [workspace.isLoaded, workspace.value, workspace.setValue]);

  // Retire a legacy `?cwd=`. Nothing reads it any more and nothing writes it, but a refresh re-requests
  // the address bar verbatim — so one left over from before the path-based groups sits there forever,
  // looking like it means something. On a bare /chat it still says which group you wanted, so upgrade
  // it to the path form rather than dropping it; anywhere else the session decides its own directory
  // and the parameter is simply removed. Other params are left alone.
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const legacyCwd = params.get('cwd');
    if (legacyCwd === null) return;
    params.delete('cwd');
    const rest = params.toString();
    const canBeUpgraded = !sessionId && !isNew && !groupCwd && legacyCwd.startsWith('/');
    const path = canBeUpgraded ? chatListPath(legacyCwd) : window.location.pathname;
    navigate(`${path}${rest ? `?${rest}` : ''}`, { replace: true });
  }, [sessionId, isNew, groupCwd, navigate]);

  // Fresh /chat/<id> (deep-link or refresh): all we have is the id. Resolve the session by id — the
  // backend scans project groups — so the cwd picker lands on its real dir AND the chat resumes, exactly
  // as clicking it from the list would. Guarded so it never clobbers an already-loaded selection.
  // Transcripts get extremely long, so we load only the tail window (?limit) and anchor to the bottom;
  // scrolling up lazy-loads older messages. `total`/`offset` tell the chat where the window sits.
  useEffect(() => {
    if (isNew) {
      // A new chat has no transcript to read a cwd from, so the group in the URL is the authority —
      // and it has to be on the selection, because that is what the composer runs in.
      setSelected({ id: `new:${Date.now()}`, cwd: groupCwd });
      return;
    }
    if (!sessionId) return;
    if (selectedRef.current?.id === sessionId && selectedRef.current.resumeSessionId) return;
    let cancelled = false;
    (async () => {
      try {
        const detail = await client.get<ClaudeSessionDetail>(`/chat/sessions/${sessionId}?limit=${CHAT_TAIL}`);
        if (cancelled) return;
        // The session's own cwd rides on the selection rather than being written back into the URL.
        // It used to do both, and the URL copy was the one the composer read — so a deep link ran its
        // first turn in the default dir during the window before this resolve landed.
        setSelected({
          id: sessionId,
          model: detail.model,
          resumeSessionId: sessionId,
          initialMessages: detail.messages as unknown as NonNullable<SelectedSession>['initialMessages'],
          total: detail.total,
          initialOffset: detail.offset,
          cwd: detail.cwd,
          title: detail.title,
          partCount: detail.partCount,
        });
      } catch (err) {
        if (cancelled) return;
        // Falling back to a bare id still opens a usable pane, but silently: you get an empty chat and
        // no hint that the transcript could not be read, which is indistinguishable from a new session.
        // Most often the id is stale — the transcript was deleted or pruned out from under the link.
        toast.error(`Could not load this conversation: ${errorText(err, 'not found')}`);
        setSelected({ id: sessionId });
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId, isNew, groupCwd]);

  return (
    <div className="h-full w-full pt-2">
      <WorkspaceView
        workspace={workspace}
        locked
        appTypes={{ allowed: ['chat-session-list', 'chat-live', 'chat-detail'], fallback: 'chat-detail' }}
        mobilePanelId={mobilePanelId}
        onMobilePanelChange={(id) => {
          // Back goes to the group's list, not the default one. On /chat/g/* that group is in the URL;
          // on /chat/<id> it isn't (deliberately — see chat-routes.ts), so fall back to the open
          // session's own directory, which the resolve above put on the selection.
          if (!id) navigate(chatListPath(groupCwd ?? selectedRef.current?.cwd ?? null), { replace: true });
        }}
      />
    </div>
  );
};
