/**
 * @file AddLaneModal.tsx
 * @description The "+ Add lane" flow, in one of two modes chosen with a
 * segmented toggle: "Repo" adopts an existing directory as-is via
 * `POST /api/lanes/ensure` (no worktree, no branch — the right mode for a
 * main repo you want stage detection on); "Worktree" provisions a
 * dashboard-managed git worktree via `POST /api/lanes/worktree` with a
 * manually-typed branch name. Either mode's path field can be filled by
 * typing, by the CwdAutocomplete suggestions, or by browsing
 * (`FolderBrowseModal`) — a native folder picker cannot hand a web page an
 * absolute filesystem path, so browsing is server-backed instead. The
 * worktree lane returned is `status: "provisioning"`: the route answers
 * before the actual `git worktree add` runs, so this component polls
 * `GET /api/lanes/:id` until that finishes before running the auto-setup
 * calls against a cwd that must actually exist on disk first.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FolderOpen } from "lucide-react";
import { ConfirmModal } from "../ConfirmModal";
import { CwdAutocomplete } from "../run/RunSetup";
import { FolderBrowseModal } from "./FolderBrowseModal";
import { api } from "../../lib/api";
import type { CwdSuggestion } from "../../lib/api";
import type { Lane } from "../../lib/types";

/** Polls the lane until the background `git worktree add` finishes (status
 *  leaves "provisioning"), or gives up after `timeoutMs`. Returns the final
 *  lane record, or `null` on timeout. */
async function waitForProvisioned(
  laneId: number,
  { intervalMs = 500, timeoutMs = 30000 } = {}
): Promise<Lane | null> {
  const deadline = Date.now() + timeoutMs;
  for (;;) {
    const { lane } = await api.lanes.get(laneId);
    if (lane.status !== "provisioning") return lane;
    if (Date.now() >= deadline) return null;
    await new Promise((r) => window.setTimeout(r, intervalMs));
  }
}

/** One segment of a two-way inline choice, styled to match RunSetup's `Seg`. */
function Seg({
  active,
  label,
  title,
  onClick,
}: {
  active: boolean;
  label: string;
  title?: string;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      title={title}
      aria-pressed={active}
      className={`flex-1 rounded px-2 py-1 text-xs font-medium transition-colors ${
        active
          ? "bg-accent text-white shadow-sm"
          : "text-fg-secondary hover:bg-surface-3 hover:text-fg-primary"
      }`}
    >
      {label}
    </button>
  );
}

export function AddLaneModal({
  open,
  onClose,
  onAdded,
  cwdSuggestions,
}: {
  open: boolean;
  onClose: () => void;
  /** Called with the newly created (possibly still-provisioning) lane. */
  onAdded: (lane: Lane) => void;
  /** The same suggestion list the Run form already fetched (dashboard cwd,
   *  home, recently-used paths) — reused rather than fetched a second time. */
  cwdSuggestions: CwdSuggestion[];
}) {
  const { t } = useTranslation(["lanes"]);
  const [mode, setMode] = useState<"repo" | "worktree">("worktree");
  const [sourceRepo, setSourceRepo] = useState("");
  const [title, setTitle] = useState("");
  const [branch, setBranch] = useState("");
  const [branches, setBranches] = useState<string[] | null>(null);
  const [base, setBase] = useState("");
  const [pipeline, setPipeline] = useState("default");
  const [pipelines, setPipelines] = useState<{ id: string; name: string; nodes: unknown[] }[]>([]);
  const [branchesError, setBranchesError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [browseOpen, setBrowseOpen] = useState(false);
  const [setupResult, setSetupResult] = useState<{
    profile: "scaffolded" | "skipped" | "failed";
    agents: "ok" | "failed";
    mcp: "ok" | "failed";
  } | null>(null);

  const reset = () => {
    setMode("worktree");
    setSourceRepo("");
    setTitle("");
    setBranch("");
    setBranches(null);
    setBase("");
    setPipeline("default");
    setBranchesError(null);
    setError(null);
    setBusy(false);
    setSetupResult(null);
  };

  // The template a lane is created with is the ONLY chance to get it right
  // from here: nothing else in the UI can change it afterwards, so a lane
  // silently born on `default` renders an 8-node map for a 16-node workflow.
  // Fetched on open (templates are file-backed and can change between opens).
  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    api.lanes
      .pipelines()
      .then((r) => {
        if (!cancelled) setPipelines(r.pipelines);
      })
      .catch(() => {
        // Quiet: the select just falls back to the single `default` option
        // below, and the lane still gets created.
        if (!cancelled) setPipelines([]);
      });
    return () => {
      cancelled = true;
    };
  }, [open]);

  // Look up the repo's branches once the path settles - debounced so every
  // keystroke while typing a path doesn't fire a request against a path that
  // isn't finished yet. Worktree mode only: "Repo" mode adopts as-is and
  // never forks a branch.
  const lookedUpFor = useRef<string>("");
  useEffect(() => {
    if (mode !== "worktree") return;
    const path = sourceRepo.trim();
    if (!path) {
      setBranches(null);
      setBase("");
      setBranchesError(null);
      return;
    }
    const timer = window.setTimeout(async () => {
      lookedUpFor.current = path;
      try {
        const r = await api.lanes.branches(path);
        if (lookedUpFor.current !== path) return; // a newer path superseded this one
        setBranches(r.branches);
        setBase(r.current || r.branches[0] || "");
        setBranchesError(null);
      } catch {
        if (lookedUpFor.current !== path) return;
        // Not yet a valid repo path (still being typed, or genuinely wrong) -
        // quiet by design, the same way CwdAutocomplete never errors either.
        setBranches(null);
        setBase("");
        setBranchesError(t("addLaneNotARepo"));
      }
    }, 300);
    return () => window.clearTimeout(timer);
  }, [mode, sourceRepo, t]);

  const submit = async () => {
    const repo = sourceRepo.trim();
    const name = title.trim();
    if (!repo) return;
    setBusy(true);
    setError(null);

    if (mode === "repo") {
      try {
        const result = await api.lanes.ensure({
          cwd: repo,
          title: name || undefined,
          pipeline,
        });
        onAdded(result.lane);
        reset();
        onClose();
      } catch (err) {
        setError(err instanceof Error ? err.message : String(err));
        setBusy(false);
      }
      return;
    }

    if (!branches || !name || !branch.trim()) return;
    try {
      const result = await api.lanes.worktree({
        sourceRepo: repo,
        title: name,
        base: base || undefined,
        branch: branch.trim(),
        pipeline,
      });
      onAdded(result.lane);

      // POST /worktree returns as soon as the DB row exists (202) — the
      // actual `git worktree add` runs afterward, in the background, on the
      // server. Firing setup against the lane's cwd before that finishes
      // means agents/mcp write into a directory that doesn't exist yet, so
      // wait for provisioning to leave the "provisioning" status first.
      const provisionedLane = await waitForProvisioned(result.lane.id);
      if (!provisionedLane || provisionedLane.status === "failed") {
        setBusy(false);
        setError(t("addLaneProvisionFailed"));
        return;
      }

      const [profileOutcome, agentsOutcome, mcpOutcome] = await Promise.allSettled([
        api.lanes.profileInit(result.lane.id),
        api.lanes.agentsInstall(result.lane.id),
        api.lanes.mcpSync(result.lane.id),
      ]);
      setSetupResult({
        profile:
          profileOutcome.status === "fulfilled"
            ? profileOutcome.value.scaffolded
              ? "scaffolded"
              : "skipped"
            : "failed",
        agents: agentsOutcome.status === "fulfilled" ? "ok" : "failed",
        mcp: mcpOutcome.status === "fulfilled" ? "ok" : "failed",
      });

      if (import.meta.env.DEV) {
        console.info("[add-lane] auto-setup result:", {
          profile:
            profileOutcome.status === "fulfilled" ? profileOutcome.value : profileOutcome.reason,
          agents: agentsOutcome.status === "fulfilled" ? agentsOutcome.value : agentsOutcome.reason,
          mcp: mcpOutcome.status === "fulfilled" ? mcpOutcome.value : mcpOutcome.reason,
        });
      }

      // Leave the modal open so the setup summary below stays on screen; the
      // user dismisses it themselves (Cancel/X) once they've seen it, rather
      // than racing a timer that can close before they've looked at it.
      setBusy(false);
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
      setBusy(false);
    }
  };

  // ConfirmModal re-focuses its Cancel button in an effect keyed on `onCancel`'s
  // identity. Every keystroke in the fields below re-renders this component; an
  // inline `() => {...}` handed to `onCancel` would get a new identity each
  // time, re-running that effect and yanking focus off the field being typed
  // into after the very first character. useCallback keeps the identity stable
  // across renders so only mount/unmount (and a real onClose change) refocuses.
  const handleCancel = useCallback(() => {
    // ConfirmModal's own Escape/backdrop/X handling calls this directly.
    // While the folder browser is open on top of it, that dismissal should
    // close only the browser, not both modals at once.
    if (browseOpen) {
      setBrowseOpen(false);
      return;
    }
    reset();
    onClose();
  }, [onClose, browseOpen]);

  const disabled =
    !!setupResult ||
    !sourceRepo.trim() ||
    (mode === "worktree" && (!title.trim() || !branches || !branch.trim()));

  return (
    <ConfirmModal
      open={open}
      title={t("addLane")}
      confirmLabel={t("add")}
      cancelLabel={t("destructive.cancel")}
      destructive={false}
      busy={busy}
      disabled={disabled}
      onConfirm={submit}
      onCancel={handleCancel}
    >
      <div className="space-y-3">
        <div className="flex items-center rounded-md border border-border bg-surface-2 p-0.5">
          <Seg
            active={mode === "repo"}
            label={t("mode.repo")}
            title={t("mode.repoHint")}
            onClick={() => setMode("repo")}
          />
          <Seg
            active={mode === "worktree"}
            label={t("mode.worktree")}
            title={t("mode.worktreeHint")}
            onClick={() => setMode("worktree")}
          />
        </div>

        <div>
          <label className="mb-1 block text-xs text-fg-secondary" htmlFor="add-lane-repo">
            {mode === "repo" ? t("addLaneRepoLabelAdopt") : t("addLaneRepoLabel")}
          </label>
          <div className="flex gap-1.5">
            <div className="min-w-0 flex-1">
              <CwdAutocomplete
                inputId="add-lane-repo"
                value={sourceRepo}
                onChange={setSourceRepo}
                suggestions={cwdSuggestions}
              />
            </div>
            <button
              type="button"
              onClick={() => setBrowseOpen(true)}
              title={t("browse.title")}
              className="flex items-center gap-1 rounded-md border border-border-light px-2 text-xs text-fg-secondary hover:bg-surface-2"
            >
              <FolderOpen className="h-3.5 w-3.5" />
              {t("browse.button")}
            </button>
          </div>
          <p className="mt-1 text-[10px] text-fg-muted">
            {mode === "repo" ? t("addLaneRepoHintAdopt") : t("addLaneRepoHint")}
          </p>
        </div>

        <div>
          <label className="mb-1 block text-xs text-fg-secondary" htmlFor="add-lane-title">
            {t("addLaneTitleLabel")}
          </label>
          <input
            id="add-lane-title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder={t("addLaneTitlePlaceholder")}
            className="w-full rounded-md border border-border-light bg-surface-0 px-3 py-1.5 text-xs text-fg-primary placeholder:text-fg-muted focus:border-blue-500 focus:outline-none"
          />
        </div>

        <div>
          <label className="mb-1 block text-xs text-fg-secondary" htmlFor="add-lane-pipeline">
            {t("addLanePipelineLabel")}
          </label>
          <select
            id="add-lane-pipeline"
            value={pipeline}
            onChange={(e) => setPipeline(e.target.value)}
            className="w-full rounded-md border border-border-light bg-surface-0 px-3 py-1.5 text-xs text-fg-primary focus:border-blue-500 focus:outline-none"
          >
            {(pipelines.length ? pipelines : [{ id: "default", name: "default", nodes: [] }]).map(
              (p) => (
                <option key={p.id} value={p.id}>
                  {p.nodes.length ? `${p.name} (${p.nodes.length})` : p.name}
                </option>
              )
            )}
          </select>
          <p className="mt-1 text-[10px] text-fg-muted">{t("addLanePipelineHint")}</p>
        </div>

        {mode === "worktree" && branches && (
          <div>
            <label className="mb-1 block text-xs text-fg-secondary" htmlFor="add-lane-base">
              {t("addLaneBaseLabel")}
            </label>
            {branches.length === 0 ? (
              <p className="text-[10px] text-fg-muted">{t("addLaneNoBranches")}</p>
            ) : (
              <select
                id="add-lane-base"
                value={base}
                onChange={(e) => setBase(e.target.value)}
                className="w-full rounded-md border border-border-light bg-surface-0 px-3 py-1.5 text-xs text-fg-primary focus:border-blue-500 focus:outline-none"
              >
                {branches.map((b) => (
                  <option key={b} value={b}>
                    {b}
                  </option>
                ))}
              </select>
            )}
          </div>
        )}
        {mode === "worktree" && branchesError && !branches && (
          <p className="text-[10px] text-status-warning">{branchesError}</p>
        )}

        {mode === "worktree" && branches && (
          <div>
            <label className="mb-1 block text-xs text-fg-secondary" htmlFor="add-lane-branch">
              {t("addLaneBranchLabel")}
            </label>
            <input
              id="add-lane-branch"
              value={branch}
              onChange={(e) => setBranch(e.target.value)}
              placeholder={t("addLaneBranchPlaceholder")}
              className="w-full rounded-md border border-border-light bg-surface-0 px-3 py-1.5 font-mono text-xs text-fg-primary placeholder:text-fg-muted focus:border-blue-500 focus:outline-none"
            />
            <p className="mt-1 text-[10px] text-fg-muted">{t("addLaneBranchHint")}</p>
          </div>
        )}

        {setupResult && (
          <div className="rounded-md border border-border-light bg-surface-0 p-2 space-y-1">
            <p className="text-[10px] font-medium text-fg-secondary">{t("addLaneSetupTitle")}</p>
            {(
              [
                {
                  label: t("addLaneSetupProfile"),
                  ok: setupResult.profile !== "failed",
                  skipped: setupResult.profile === "skipped",
                },
                { label: t("addLaneSetupAgents"), ok: setupResult.agents === "ok" },
                { label: t("addLaneSetupMcp"), ok: setupResult.mcp === "ok" },
              ] as const
            ).map((row) => (
              <p key={row.label} className="flex items-center gap-1.5 text-[11px] text-fg-primary">
                <span
                  className={
                    "skipped" in row && row.skipped
                      ? "text-fg-muted"
                      : row.ok
                        ? "text-status-success"
                        : "text-status-danger"
                  }
                >
                  {"skipped" in row && row.skipped ? "–" : row.ok ? "✓" : "✗"}
                </span>
                {row.label}
              </p>
            ))}
          </div>
        )}

        {error && (
          <p role="alert" className="text-xs text-status-danger">
            {error}
          </p>
        )}
      </div>

      <FolderBrowseModal
        open={browseOpen}
        initialPath={sourceRepo.trim() || undefined}
        onSelect={setSourceRepo}
        onClose={() => setBrowseOpen(false)}
      />
    </ConfirmModal>
  );
}
