"use client";

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

import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { requestDashboardApi } from "@/lib/dashboard-api-client";

import type { RunStatus } from "../_lib/schema";

const RUN_STATUS_HREF = "/dashboard/master-table/structure-master/bulk-edit/run-status";
const POLL_INTERVAL_MS = 2_000;

const PHASE_LABELS: Record<string, string> = {
  queued: "Waiting for the runner",
  master: "Updating structure master",
  temp: "Replaying temp rows (oldest first)",
  trx: "Writing client portfolios",
  done: "Finished",
};

type Props = {
  run: RunStatus;
  onSettled?: (run: RunStatus) => void;
  /** Offered only while the run has not started; see the queued-stall notice. */
  onCancel?: () => void;
  isCancelling?: boolean;
};

/**
 * The run is drained by a cron-driven console command, so the browser polls
 * rather than holding a request open.
 *
 * A queued run only advances once `corporate-action/run` fires, which is why a
 * long spell at 0% is called out explicitly — otherwise a cron that is not
 * installed looks identical to a slow job.
 */
export function ApplyProgress({ run: initialRun, onSettled, onCancel, isCancelling }: Props) {
  const [run, setRun] = React.useState(initialRun);
  const [pollError, setPollError] = React.useState<string | null>(null);
  const [waitedTicks, setWaitedTicks] = React.useState(0);
  const onSettledRef = React.useRef(onSettled);

  React.useEffect(() => {
    onSettledRef.current = onSettled;
  }, [onSettled]);

  const isSettled = run.status === "done" || run.status === "failed";

  React.useEffect(() => {
    if (isSettled) {
      onSettledRef.current?.(run);
      return;
    }

    const controller = new AbortController();
    const timer = window.setInterval(() => {
      requestDashboardApi<{ run?: RunStatus | null }>({
        url: `${RUN_STATUS_HREF}?run_id=${run.runId}`,
        method: "GET",
        cache: "no-store",
        signal: controller.signal,
        fallbackError: "Could not read the run status.",
      })
        .then((data) => {
          setPollError(null);
          setWaitedTicks((ticks) => ticks + 1);
          if (data.run) {
            setRun(data.run);
          }
        })
        .catch((error: unknown) => {
          if (!controller.signal.aborted) {
            setPollError(
              error instanceof Error ? error.message : "Could not read the run status.",
            );
          }
        });
    }, POLL_INTERVAL_MS);

    return () => {
      controller.abort();
      window.clearInterval(timer);
    };
  }, [isSettled, run, run.runId]);

  const percent = run.total > 0 ? run.percent : run.status === "done" ? 100 : 0;
  const stalledInQueue = run.status === "queued" && waitedTicks > 10;

  return (
    <div className="space-y-3 rounded-lg border bg-card p-4">
      <div className="flex items-center gap-2">
        {run.status === "done" ? (
          <CheckCircle2 className="size-4 text-emerald-600 dark:text-emerald-500" />
        ) : run.status === "failed" ? (
          <CircleX className="size-4 text-destructive" />
        ) : (
          <Loader2 className="size-4 animate-spin text-muted-foreground" />
        )}
        <span className="font-medium text-sm">
          {PHASE_LABELS[run.phase] ?? run.phase}
        </span>
        <span className="ml-auto text-muted-foreground text-sm tabular-nums">
          {run.processed}/{run.total}
        </span>
      </div>

      <Progress value={percent} />

      <div className="flex flex-wrap gap-x-4 gap-y-1 text-muted-foreground text-xs">
        <span>Run #{run.runId}</span>
        {run.cursorDate ? <span>At date {run.cursorDate}</span> : null}
        {run.startedAt ? <span>Started {run.startedAt}</span> : null}
        {run.finishedAt ? <span>Finished {run.finishedAt}</span> : null}
      </div>

      {stalledInQueue ? (
        <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
          <p className="text-amber-600 text-xs dark:text-amber-500">
            Still queued. The split is executed by the{" "}
            <code className="font-mono">corporate-action/run</code> console command —
            check that its cron entry is installed and running.
          </p>
          {onCancel ? (
            <Button
              type="button"
              size="sm"
              variant="outline"
              onClick={onCancel}
              disabled={isCancelling}
            >
              {isCancelling ? "Cancelling…" : "Cancel and edit"}
            </Button>
          ) : null}
        </div>
      ) : null}

      {pollError ? <p className="text-destructive text-xs">{pollError}</p> : null}

      {run.log ? (
        <pre className="max-h-40 overflow-auto rounded-md bg-muted/40 p-3 text-xs whitespace-pre-wrap">
          {run.log}
        </pre>
      ) : null}
    </div>
  );
}
