1import { useAtomRefresh, useAtomValue } from "@effect/atom-react";
2import {
3 descriptionDraftInForce,
4 settleDescriptionSave,
5 startDescriptionSave,
6 type DescriptionDraft,
7} from "@t3tools/client-runtime/pull-request-editing";
8import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
9import type { PullRequestDetail } from "@t3tools/contracts";
10import { setMarkdownTaskChecked } from "@t3tools/shared/markdownTaskList";
11import { AsyncResult } from "effect/reactivity";
12import { useRef, useState } from "react";
13import * as Dialog from "../../components/ui/Dialog";
14import { composerPullRequests } from "../../state/pull-requests";
15import { useAtomCommand } from "../../state/use-atom-command";
16import { pullRequestTarget, type PullRequestParams } from "./usePullRequestDiff";
17
18/** The description with checkbox ticks shown before the host confirms them. */
19export function usePullRequestDescription(
20 params: PullRequestParams,
21 pr: PullRequestDetail | null,
22 canEdit: boolean,
23) {
24 const target = pullRequestTarget(params);
25 const update = useAtomCommand(composerPullRequests.update, { reportFailure: false });
26 const refreshDetail = useAtomRefresh(composerPullRequests.detail(target));
27 const reading = useAtomValue(composerPullRequests.detailReadVersion(target));
28 const savesRunning = useRef(0);
29 const [draft, setDraft] = useState<DescriptionDraft | null>(null);
30 const kept = descriptionDraftInForce(draft, pr?.url ?? "", reading);
31 if (kept !== draft) setDraft(kept);
32 const body = kept?.body ?? pr?.body ?? "";
33 const save = async (detail: PullRequestDetail, next: string) => {
34 setDraft((current) =>
35 startDescriptionSave(current, { url: detail.url, observedAt: reading }, next),
36 );
37 savesRunning.current += 1;
38 const response = await update({ ...target, input: { ...target.input, body: next } });
39 savesRunning.current -= 1;
40 const saved = !AsyncResult.isFailure(response);
41 setDraft((current) => settleDescriptionSave(current, detail.url, saved));
42 if (AsyncResult.isFailure(response)) {
43 const cause = squashAtomCommandFailure(response);
44 Dialog.alert(
45 "Could not save the description",
46 cause instanceof Error ? cause.message : "Try again.",
47 );
48 }
49 if (savesRunning.current === 0) refreshDetail();
50 };
51 return {
52 body,
53 /** The edit screen starts from the host's copy, so it waits for a reading with the ticks. */
54 pending: kept !== null,
55 toggleTask:
56 canEdit && pr !== null
57 ? ({ markerOffset, checked }: { markerOffset: number; checked: boolean }) => {
58 const next = setMarkdownTaskChecked(body, markerOffset, checked);
59 if (next === body) return false;
60 void save(pr, next);
61 return true;
62 }
63 : undefined,
64 };
65}