1import type { EnvironmentId, PullRequestRef } from "@t3tools/contracts";
2import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
3import { useAtomValue } from "@effect/atom-react";
4import { useEffect, useEffectEvent, useState } from "react";
5
6import { pullRequestEnvironment } from "~/state/pullRequests";
7import { useEnvironmentQuery } from "~/state/query";
8import { useAtomCommand } from "~/state/use-atom-command";
9
10import { toastManager } from "../../ui/toast";
11import { readableFailure } from "../pullRequestDetail.logic";
12import { guideRunKey, useGuideRunStore } from "./guideRunStore";
13
14/**
15 * A pull request's stored guide and the way to write a new one, shared by the Guide tab and the
16 * header menu. Starting a run hands it to the run coordinator, which re-reads it until it ends
17 * and says so if the reader is not looking at the guide.
18 */
19export function useReviewGuideWriter({
20 environmentId,
21 reference,
22 title,
23}: {
24 environmentId: EnvironmentId;
25 reference: PullRequestRef;
26 /** The pull request's title for the notice when the guide is ready. */
27 title: string | null;
28}) {
29 const query = useEnvironmentQuery(
30 pullRequestEnvironment.reviewGuide({ environmentId, input: reference }),
31 );
32 const canWrite = useAtomValue(
33 pullRequestEnvironment.generateReviewGuide.permissionAtom(environmentId),
34 );
35 const generate = useAtomCommand(pullRequestEnvironment.generateReviewGuide, {
36 reportFailure: false,
37 });
38 const [starting, setStarting] = useState(false);
39 const data = query.data;
40 const running = starting || data?.run?._tag === "Running";
41
42 const watchRun = useEffectEvent(() =>
43 useGuideRunStore.getState().watch({
44 environmentId,
45 reference,
46 title,
47 previousGuideAt: data?.guide?.generatedAt ?? null,
48 previousFailedAt: data?.run?._tag === "Failed" ? data.run.failedAt : null,
49 since: Date.now(),
50 }),
51 );
52 // A run started elsewhere, or before a reload, is watched as soon as it is seen.
53 useEffect(() => {
54 if (data?.run?._tag === "Running") watchRun();
55 }, [data?.run?._tag]);
56
57 const write = async () => {
58 if (running) return;
59 setStarting(true);
60 watchRun();
61 const result = await generate({ environmentId, input: reference });
62 setStarting(false);
63 if (result._tag === "Failure") {
64 // Nothing started, so there is nothing to watch for and nothing more to announce.
65 useGuideRunStore.getState().settle(guideRunKey(environmentId, reference));
66 toastManager.add({
67 type: "error",
68 title: "Could not start a review guide",
69 description: readableFailure(
70 squashAtomCommandFailure(result),
71 "Check the review guide model in Settings → Source Control.",
72 ),
73 });
74 }
75 };
76
77 return { query, data, running, canWrite, write };
78}