"use client";

import * as React from "react";
import { Loader2, Sparkles } from "lucide-react";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";

/**
 * Editor for a backlog item's `workflow_prompt` override.
 *
 * Three visible parts:
 *   1. Source badge — where the effective prompt actually comes from
 *      (the task itself, an inherited epic/plan, or the built-in default).
 *   2. Effective prompt preview — read-only, what an agent would receive
 *      from `claim_task` right now.
 *   3. Override textarea — the editable knob. Empty/whitespace clears the
 *      override and reverts to inheritance.
 *
 * Edit affordances (Save / Clear) are hidden entirely when the caller
 * isn't an owner/admin so the click can't fail. The server still enforces
 * the same gate on `backlog.updateWorkflowPrompt` — UI hiding is a
 * convenience, not a security boundary.
 */

const MAX_PROMPT_LENGTH = 20_000;

type Source = "task" | "epic" | "plan" | "default";

type SourceContext = {
  /** Title to render inside the source badge (e.g. epic title). */
  taskTitle: string;
  epicTitle: string | null;
  planTitle: string | null;
};

function describeSource(source: Source, ctx: SourceContext): string {
  switch (source) {
    case "task":
      return "From this task";
    case "epic":
      return ctx.epicTitle
        ? `Inherited from epic: ${ctx.epicTitle}`
        : "Inherited from epic";
    case "plan":
      return ctx.planTitle
        ? `Inherited from plan: ${ctx.planTitle}`
        : "Inherited from plan";
    case "default":
      return "Built-in default";
  }
}

function badgeVariantForSource(
  source: Source,
): "default" | "secondary" | "muted" {
  if (source === "task") return "default";
  if (source === "default") return "muted";
  return "secondary";
}

export function WorkflowPromptSection({
  workspace,
  backlogItemId,
  canEdit,
  sourceContext,
}: {
  /** Workspace UUID or slug — passed to every tRPC call. */
  workspace: string;
  backlogItemId: string;
  canEdit: boolean;
  sourceContext: SourceContext;
}) {
  const utils = api.useUtils();
  const promptQuery = api.backlog.getWorkflowPrompt.useQuery(
    { workspace, backlogItemId },
    { enabled: Boolean(workspace && backlogItemId) },
  );

  // Draft is initialized from the server's ownOverride and kept locally
  // until save/clear. We track the last loaded value so re-renders that
  // come from a successful invalidation don't stomp on unrelated edits
  // the user might have made in the textarea between request and refetch.
  const [draft, setDraft] = React.useState<string>("");
  const [lastLoaded, setLastLoaded] = React.useState<string | null>(null);
  React.useEffect(() => {
    const next = promptQuery.data?.ownOverride ?? "";
    if (promptQuery.data && next !== lastLoaded) {
      setDraft(next);
      setLastLoaded(next);
    }
  }, [promptQuery.data, lastLoaded]);

  const updateMut = api.backlog.updateWorkflowPrompt.useMutation({
    onSuccess: async () => {
      // Re-fetch the resolved prompt + source. Don't optimistically
      // mutate — the source badge can flip in non-obvious ways
      // (e.g. clearing a task override might re-expose an epic or plan
      // inheritance rather than the default), so it's safer to round-trip.
      await utils.backlog.getWorkflowPrompt.invalidate({
        workspace,
        backlogItemId,
      });
    },
  });

  if (promptQuery.isLoading) {
    return (
      <section className="space-y-3 rounded-lg border bg-card p-4">
        <Skeleton className="h-5 w-40" />
        <Skeleton className="h-24 w-full" />
        <Skeleton className="h-32 w-full" />
      </section>
    );
  }

  if (promptQuery.error) {
    return (
      <section className="rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive">
        Couldn&apos;t load workflow prompt: {promptQuery.error.message}
      </section>
    );
  }

  const data = promptQuery.data;
  if (!data) return null;

  const source = data.source;
  const ownOverride = data.ownOverride ?? "";
  const hasOverride = Boolean(ownOverride.trim());
  const dirty = draft !== ownOverride;
  const tooLong = draft.length > MAX_PROMPT_LENGTH;

  return (
    <section className="space-y-4 rounded-lg border bg-card p-4">
      <header className="flex flex-wrap items-center gap-3">
        <div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary/10">
          <Sparkles className="size-4 text-primary" />
        </div>
        <div className="flex-1">
          <h2 className="text-sm font-semibold">Workflow prompt</h2>
          <p className="text-xs text-muted-foreground">
            What an agent receives when it claims this task via MCP.
          </p>
        </div>
        <Badge
          variant={badgeVariantForSource(source)}
          className="font-normal"
          title={`source=${source}`}
        >
          {describeSource(source, sourceContext)}
        </Badge>
      </header>

      <div>
        <div className="mb-1 text-[10px] uppercase tracking-wide text-muted-foreground">
          Effective prompt
        </div>
        <pre
          className={cn(
            "whitespace-pre-wrap rounded-md border bg-muted/40 px-3 py-2 font-mono text-xs leading-relaxed",
            "text-foreground",
          )}
        >
          {data.effectivePrompt}
        </pre>
      </div>

      <div>
        <div className="mb-1 flex items-center justify-between">
          <label
            htmlFor="workflow-prompt-override"
            className="text-[10px] uppercase tracking-wide text-muted-foreground"
          >
            Override for this task
          </label>
          {hasOverride ? (
            <span className="text-[10px] text-muted-foreground">
              Currently overriding inherited prompt
            </span>
          ) : (
            <span className="text-[10px] text-muted-foreground">
              Empty = inherit
            </span>
          )}
        </div>
        <textarea
          id="workflow-prompt-override"
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          disabled={!canEdit || updateMut.isPending}
          rows={8}
          maxLength={MAX_PROMPT_LENGTH + 100}
          placeholder={
            canEdit
              ? "Leave empty to inherit from the epic, plan, or built-in default."
              : "Only owners and admins can edit this prompt."
          }
          className={cn(
            "block w-full resize-y rounded-md border border-input bg-background px-3 py-2 font-mono text-xs leading-relaxed",
            "ring-offset-background placeholder:text-muted-foreground",
            "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
            "disabled:cursor-not-allowed disabled:opacity-60",
          )}
        />
        <div className="mt-1 flex items-center justify-between text-[10px] text-muted-foreground">
          <span className={tooLong ? "text-destructive" : undefined}>
            {draft.length.toLocaleString()} / {MAX_PROMPT_LENGTH.toLocaleString()}
          </span>
          {updateMut.error ? (
            <span className="text-destructive">
              {updateMut.error.message}
            </span>
          ) : null}
        </div>
      </div>

      {canEdit ? (
        <div className="flex items-center justify-end gap-2">
          {hasOverride ? (
            <Button
              type="button"
              variant="outline"
              size="sm"
              disabled={updateMut.isPending}
              onClick={() => {
                updateMut.mutate({
                  workspace,
                  backlogItemId,
                  workflowPrompt: null,
                });
                setDraft("");
              }}
            >
              {updateMut.isPending && updateMut.variables?.workflowPrompt === null ? (
                <Loader2 className="size-3 animate-spin" />
              ) : (
                "Clear override"
              )}
            </Button>
          ) : null}
          <Button
            type="button"
            size="sm"
            disabled={!dirty || tooLong || updateMut.isPending}
            onClick={() =>
              updateMut.mutate({
                workspace,
                backlogItemId,
                workflowPrompt: draft.trim() ? draft : null,
              })
            }
          >
            {updateMut.isPending && updateMut.variables?.workflowPrompt !== null ? (
              <Loader2 className="size-3 animate-spin" />
            ) : (
              "Save"
            )}
          </Button>
        </div>
      ) : null}
    </section>
  );
}
