/**
 * @file RunSetup.tsx
 * @description The pre-run setup panel: everything the user picks before a run
 * exists. Moved verbatim out of `pages/Run.tsx` (where it was `ConfigCard`) so
 * the Run page and the Workspace page can both mount the same panel.
 *
 * What lives here:
 *   - `RunSetup` — fresh-vs-resume source, the prompt editor, and the cwd /
 *     model / permission-mode / effort fields, plus the concurrency hint and
 *     the Start button. Its disabled state is driven by the `binaryFound` prop,
 *     so a missing `claude` binary is a surfaced state here rather than a probe
 *     of its own.
 *   - the pickers the panel owns: `CwdAutocomplete`, `SessionPicker`,
 *     `ModelPicker`, and the small `Field` layout helper.
 *
 * Props only for `RunSetup`: no `/stage` call, no lane API call, and no run
 * lifecycle — the page owns `api.run.start` and hands the result back through
 * `onStart`. `SessionPicker` keeps the one API call it already owned (listing
 * past sessions to resume) and `PromptEditor` keeps its `@`-file lookup.
 *
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
  Play,
  RefreshCw,
  AlertCircle,
  ChevronDown,
  ShieldAlert,
  X,
  FolderOpen,
  Home,
  History as HistoryIcon,
  Search,
  RotateCcw,
  Lock,
} from "lucide-react";
import { api, RUN_MODEL_CHOICES, RUN_EFFORT_CHOICES } from "../../lib/api";
import type {
  CwdSuggestion,
  DashboardRunHistoryItem,
  RunListResponse,
  EffortLevel,
  PermissionMode,
  RunStartArgs,
} from "../../lib/api";
import type { Session } from "../../lib/types";
import { Select } from "../Select";

// Minimal SlashCommand type (from deleted RunConsole)
export interface SlashCommand {
  name: string;
  source: "project" | "user" | "plugin" | "builtin";
  description?: string;
}

// Minimal PromptEditor component (from deleted RunConsole)
interface PromptEditorProps {
  value: string;
  onChange: (s: string) => void;
  onSubmit: () => void;
  placeholder: string;
  rows?: number;
  slashCommands: SlashCommand[];
  fileCwd: string;
}

function PromptEditor({ value, onChange, onSubmit, placeholder, rows = 5 }: PromptEditorProps) {
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
      e.preventDefault();
      onSubmit();
    }
  };

  return (
    <textarea
      value={value}
      onChange={(e) => onChange(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder={placeholder}
      rows={rows}
      className="w-full bg-surface-2 border border-border rounded-md px-3 py-2 text-[11px] font-mono text-fg-primary placeholder:text-fg-muted focus:outline-none focus:border-accent/50 resize-none"
    />
  );
}

// ── Limitations banner (above the config card) ────────────────────────

interface RunSetupProps {
  laneId: number;
  prompt: string;
  onPromptChange: (s: string) => void;
  cwd: string;
  onCwdChange: (s: string) => void;
  cwdSuggestions: CwdSuggestion[];
  model: string;
  onModelChange: (s: string) => void;
  permissionMode: PermissionMode;
  onPermissionModeChange: (m: PermissionMode) => void;
  effort: EffortLevel;
  onEffortChange: (e: EffortLevel) => void;
  binaryFound: boolean;
  busy: boolean;
  onStart: (args: RunStartArgs) => void;
  activeRuns: RunListResponse | null;
  resumeSession: Session | null;
  onResumeSessionChange: (s: Session | null) => void;
  /** The selected lane's cwd - narrows the resume picker to that lane's own
   *  sessions. Undefined when no lane is selected (the picker then lists
   *  everything, same as before lanes existed). */
  laneCwd?: string;
  slashCommands?: SlashCommand[];
  runHistory: DashboardRunHistoryItem[];
  onResumeFromHistory: (item: DashboardRunHistoryItem) => void;
}

export function RunSetup(props: RunSetupProps) {
  const { t } = useTranslation("run");
  const atCap = false; // TODO: re-add when concurrency info is available
  const isResume = !!props.resumeSession;
  const [resumePicked, setResumePicked] = useState(isResume);
  // Keep "resume picked" in sync with the parent. Parent set a resume session
  // (e.g. user clicked Resume in the runs modal) - flip the radio so the picker
  // is shown and the selection is visible.
  useEffect(() => {
    if (isResume && !resumePicked) setResumePicked(true);
  }, [isResume, resumePicked]);

  return (
    <div className="rounded-xl border border-border bg-surface-1">
      {/* Fresh vs resume source picker */}
      <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 border-b border-border px-3 py-2 text-[11.5px]">
        <div className="flex items-center rounded-md border border-border bg-surface-2 p-0.5">
          <Seg
            active={!resumePicked}
            label={t("resume.freshOption")}
            title={t("resume.freshHint")}
            onClick={() => {
              setResumePicked(false);
              props.onResumeSessionChange(null);
            }}
          />
          <Seg
            active={resumePicked}
            label={t("resume.resumeOption")}
            title={t("resume.resumeHint")}
            onClick={() => setResumePicked(true)}
          />
        </div>
        {resumePicked && (
          <div className="min-w-0 flex-1">
            <SessionPicker
              selected={props.resumeSession}
              onSelect={props.onResumeSessionChange}
              cwd={props.laneCwd}
            />
          </div>
        )}
      </div>

      {/* Prompt */}
      <div className="px-4 py-3 border-b border-border">
        <label className="block text-[11px] font-semibold uppercase tracking-wider text-fg-muted mb-1.5">
          {t("fields.prompt")}
        </label>
        <PromptEditor
          value={props.prompt}
          onChange={props.onPromptChange}
          onSubmit={() => handleStart(props)}
          placeholder={t("fields.promptPlaceholderTerminal")}
          rows={5}
          slashCommands={props.slashCommands ?? []}
          fileCwd={props.resumeSession?.cwd || props.cwd}
        />
        <div className="mt-1 text-[10px] text-fg-muted">
          {t("hint.shortcut")} · / for slash commands · @ for file references
        </div>
      </div>

      {/* Advanced fields */}
      <div className="grid grid-cols-1 gap-3 px-4 py-3 sm:grid-cols-2 lg:grid-cols-4">
        <Field label={t("fields.cwd")}>
          {isResume && props.resumeSession ? (
            <div className="bg-surface-2 border border-border rounded-md px-3 py-1.5 text-[11px] font-mono text-fg-secondary flex items-center gap-2">
              <Lock className="w-3 h-3 text-fg-muted flex-shrink-0" />
              <span className="truncate">{props.resumeSession.cwd}</span>
            </div>
          ) : (
            <div title={t("fields.cwdHint")}>
              <CwdAutocomplete
                value={props.cwd}
                onChange={props.onCwdChange}
                suggestions={props.cwdSuggestions}
              />
            </div>
          )}
        </Field>
        <Field label={t("fields.model")}>
          <ModelPicker value={props.model} onChange={props.onModelChange} />
        </Field>
        <Field label={t("fields.permissionMode")}>
          <Select<PermissionMode>
            value={props.permissionMode}
            onChange={props.onPermissionModeChange}
            options={[
              { value: "acceptEdits", label: t("fields.permissionAcceptEdits") },
              { value: "default", label: t("fields.permissionDefault") },
              { value: "plan", label: t("fields.permissionPlan") },
              { value: "bypassPermissions", label: t("fields.permissionBypass") },
            ]}
          />
        </Field>
        <Field label={t("fields.effort")}>
          <Select<EffortLevel>
            value={props.effort}
            onChange={props.onEffortChange}
            options={RUN_EFFORT_CHOICES.map((c) => ({
              value: c.id,
              label: c.label,
              hint: c.hint,
            }))}
          />
        </Field>
      </div>

      {props.permissionMode === "bypassPermissions" && (
        <div className="mx-4 mb-3 rounded-md border border-status-danger/40 bg-status-danger/10 px-3 py-2 text-[11px] text-status-danger flex items-start gap-2">
          <ShieldAlert className="w-3.5 h-3.5 flex-shrink-0 mt-0.5" />
          <span>{t("hint.permissionWarning")}</span>
        </div>
      )}

      {/* Footer: contextual run-state hint + run button */}
      <div className="border-t border-border px-4 py-3 flex items-center justify-between gap-3 flex-wrap">
        <div className="flex items-center gap-3 text-[11px] min-w-0">
          {atCap ? (
            <span className="inline-flex items-center gap-1.5 text-status-warning">
              <AlertCircle className="w-3.5 h-3.5" />
              {t("concurrency.atCap", { max: 0 })}
            </span>
          ) : null}
        </div>
        <button
          onClick={() => handleStart(props)}
          disabled={
            !props.binaryFound ||
            !props.prompt.trim() ||
            props.busy ||
            atCap ||
            (resumePicked && !props.resumeSession) ||
            // Resume locks cwd to the original session, so allow it then;
            // otherwise require a non-empty cwd so we never spawn at an
            // invisible default.
            (!props.resumeSession && !props.cwd.trim())
          }
          className="inline-flex items-center gap-2 rounded-lg border border-accent/40 bg-accent/15 hover:bg-accent/25 text-accent px-4 py-1.5 text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
        >
          {props.busy ? (
            <RefreshCw className="w-3.5 h-3.5 animate-spin" />
          ) : (
            <Play className="w-3.5 h-3.5" />
          )}
          {props.busy ? t("actions.starting") : t("actions.start")}
        </button>
      </div>
    </div>
  );
}

function handleStart(props: RunSetupProps) {
  props.onStart({
    laneId: props.laneId,
    cwd: props.cwd || undefined,
    model: props.model || undefined,
    permissionMode: props.permissionMode || undefined,
    effort: props.effort || undefined,
    resumeSessionId: props.resumeSession?.id || undefined,
    initialPrompt: props.prompt || undefined,
  });
}

/** One segment of a two-way inline choice. The explanation rides on `title`
 *  instead of a hint line, which is what keeps the row to one line. */
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={`rounded px-2 py-0.5 font-medium transition-colors ${
        active ? "bg-accent/20 text-accent" : "text-fg-secondary hover:text-fg-primary"
      }`}
    >
      {label}
    </button>
  );
}

function Field({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div>
      <label className="block text-[10px] font-semibold uppercase tracking-wider text-fg-muted mb-1">
        {label}
      </label>
      {children}
    </div>
  );
}

// ── CWD autocomplete ──────────────────────────────────────────────────

export function CwdAutocomplete({
  value,
  onChange,
  suggestions,
  inputId,
}: {
  value: string;
  onChange: (s: string) => void;
  suggestions: CwdSuggestion[];
  /** Sets the input's `id` so an external `<label htmlFor>` can target it. */
  inputId?: string;
}) {
  const { t } = useTranslation("run");
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState(0);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const inputRef = useRef<HTMLInputElement | null>(null);

  // Close dropdown on outside click
  useEffect(() => {
    if (!open) return;
    const onClick = (e: MouseEvent) => {
      if (!containerRef.current) return;
      if (!containerRef.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
  }, [open]);

  const filtered = useMemo(() => {
    const q = value.toLowerCase().trim();
    const out = suggestions.filter(
      (s) => !q || s.path.toLowerCase().includes(q) || s.label.toLowerCase().includes(q)
    );
    return out;
  }, [value, suggestions]);

  // Group suggestions by kind preserving fixed order — home first, matching
  // the neutral default the page pre-fills (issue #202).
  const groups = useMemo(() => {
    const order: CwdSuggestion["kind"][] = ["home", "dashboard", "recent"];
    return order
      .map((kind) => ({ kind, items: filtered.filter((s) => s.kind === kind) }))
      .filter((g) => g.items.length > 0);
  }, [filtered]);

  // Flat index for keyboard navigation
  const flat = useMemo(() => groups.flatMap((g) => g.items), [groups]);

  // Keep `active` clamped within bounds
  useEffect(() => {
    if (active >= flat.length) setActive(Math.max(0, flat.length - 1));
  }, [flat.length, active]);

  const choose = (s: CwdSuggestion) => {
    onChange(s.path);
    setOpen(false);
    inputRef.current?.blur();
  };

  const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
      setOpen(true);
      e.preventDefault();
      return;
    }
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setActive((a) => Math.min(flat.length - 1, a + 1));
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setActive((a) => Math.max(0, a - 1));
    } else if (e.key === "Enter") {
      if (open && flat[active]) {
        e.preventDefault();
        choose(flat[active]);
      }
    } else if (e.key === "Escape") {
      setOpen(false);
    }
  };

  return (
    <div ref={containerRef} className="relative">
      <div className="relative">
        <FolderOpen className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-fg-muted pointer-events-none" />
        <input
          ref={inputRef}
          id={inputId}
          type="text"
          value={value}
          onChange={(e) => {
            onChange(e.target.value);
            setOpen(true);
            setActive(0);
          }}
          onFocus={() => setOpen(true)}
          onKeyDown={onKeyDown}
          placeholder={t("fields.cwdPlaceholder")}
          autoComplete="off"
          spellCheck={false}
          className="w-full bg-surface-2 border border-border rounded-md pl-7 pr-3 py-1.5 text-[11px] font-mono text-fg-primary placeholder:text-fg-muted focus:outline-none focus:border-accent/50"
        />
      </div>
      {open && (
        <div className="absolute z-30 left-0 right-0 mt-1 rounded-md border border-border bg-surface-1 shadow-lg shadow-black/40 max-h-72 overflow-auto py-1">
          {groups.length === 0 ? (
            <div className="px-3 py-2 text-[11px] text-fg-muted">{t("fields.cwdNoMatches")}</div>
          ) : (
            groups.map((g) => (
              <div key={g.kind}>
                <div className="px-3 pt-1.5 pb-0.5 text-[10px] font-semibold uppercase tracking-wider text-fg-muted flex items-center gap-1.5">
                  {g.kind === "dashboard" ? (
                    <FolderOpen className="w-3 h-3" />
                  ) : g.kind === "home" ? (
                    <Home className="w-3 h-3" />
                  ) : (
                    <HistoryIcon className="w-3 h-3" />
                  )}
                  {t(`fields.cwdGroups.${g.kind}`)}
                </div>
                {g.items.map((s) => {
                  const idx = flat.indexOf(s);
                  const isActive = idx === active;
                  return (
                    <button
                      key={s.path}
                      type="button"
                      onMouseDown={(e) => e.preventDefault() /* keep input focused */}
                      onClick={() => choose(s)}
                      onMouseEnter={() => setActive(idx)}
                      className={`w-full text-left px-3 py-1.5 transition-colors ${
                        isActive ? "bg-accent/15" : "hover:bg-surface-3"
                      }`}
                    >
                      <div className="text-[11px] text-fg-secondary truncate">{s.label}</div>
                      <div className="font-mono text-[10px] text-fg-muted truncate">{s.path}</div>
                    </button>
                  );
                })}
              </div>
            ))
          )}
        </div>
      )}
    </div>
  );
}

// ── Model picker ──────────────────────────────────────────────────────

// ── Session picker (for resume) ───────────────────────────────────────

function SessionPicker({
  selected,
  onSelect,
  cwd,
}: {
  selected: Session | null;
  onSelect: (s: Session | null) => void;
  /** Restricts the list to sessions under this exact working directory - the
   *  lane a resume is started from should only offer that lane's own history. */
  cwd?: string;
}) {
  const { t } = useTranslation("run");
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [sessions, setSessions] = useState<Session[] | null>(null);
  const containerRef = useRef<HTMLDivElement | null>(null);

  // Lazily load sessions when the picker opens, and again whenever the lane
  // (and so its cwd filter) changes - otherwise switching lanes would leave
  // the picker showing the PREVIOUS lane's sessions until closed and reopened.
  const fetchedForCwd = useRef<string | undefined>(undefined);
  useEffect(() => {
    if (!open) return;
    if (sessions !== null && fetchedForCwd.current === cwd) return;
    fetchedForCwd.current = cwd;
    api.sessions
      .list({ sort_by: "started_at", sort_desc: true, limit: 100, cwd })
      .then((r) => setSessions(r.sessions))
      .catch(() => setSessions([]));
  }, [open, cwd, sessions]);

  useEffect(() => {
    if (!open) return;
    const onClick = (e: MouseEvent) => {
      if (!containerRef.current) return;
      if (!containerRef.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
  }, [open]);

  const filtered = useMemo(() => {
    if (!sessions) return [];
    const q = query.toLowerCase().trim();
    if (!q) return sessions;
    return sessions.filter(
      (s) =>
        s.id.toLowerCase().includes(q) ||
        (s.cwd || "").toLowerCase().includes(q) ||
        (s.status || "").toLowerCase().includes(q)
    );
  }, [sessions, query]);

  if (selected) {
    return (
      <div className="mt-2 rounded-lg border border-accent/30 bg-accent/5 px-3 py-2 flex items-start gap-2">
        <RotateCcw className="w-3.5 h-3.5 text-accent flex-shrink-0 mt-0.5" />
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2 flex-wrap">
            <span className="text-[10px] font-mono px-1.5 py-0.5 rounded bg-accent/15 text-accent border border-accent/30">
              {t("resume.selectedBadge")}
            </span>
            <span className="font-mono text-[11px] text-fg-secondary truncate">{selected.id}</span>
          </div>
          <div className="font-mono text-[10px] text-fg-muted truncate mt-0.5">{selected.cwd}</div>
        </div>
        <button
          onClick={() => onSelect(null)}
          className="text-[10px] font-medium px-2 py-0.5 rounded border border-border bg-surface-2 hover:bg-surface-3 text-fg-secondary inline-flex items-center gap-1 flex-shrink-0"
        >
          <X className="w-3 h-3" />
          {t("resume.clear")}
        </button>
      </div>
    );
  }

  return (
    <div ref={containerRef} className="relative mt-2">
      <button
        onClick={() => setOpen((v) => !v)}
        className="w-full text-left rounded-md border border-dashed border-border bg-surface-2 hover:bg-surface-3 px-3 py-2 text-[11px] text-fg-secondary inline-flex items-center gap-2"
      >
        <RotateCcw className="w-3.5 h-3.5" />
        {t("resume.pickSession")}
        <ChevronDown className="w-3 h-3 opacity-70 ml-auto" />
      </button>
      {open && (
        <div className="absolute z-30 left-0 right-0 mt-1 rounded-md border border-border bg-surface-1 shadow-lg shadow-black/40 overflow-hidden">
          <div className="px-3 py-2 border-b border-border flex items-center gap-2">
            <Search className="w-3.5 h-3.5 text-fg-muted flex-shrink-0" />
            <input
              autoFocus
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              placeholder={t("resume.search")}
              className="bg-transparent text-[11px] text-fg-primary placeholder:text-fg-muted focus:outline-none w-full"
            />
          </div>
          <div className="max-h-72 overflow-auto py-1">
            {sessions === null ? (
              <div className="px-3 py-2 text-[11px] text-fg-muted">…</div>
            ) : filtered.length === 0 ? (
              <div className="px-3 py-2 text-[11px] text-fg-muted">{t("resume.noSessions")}</div>
            ) : (
              filtered.map((s) => (
                <button
                  key={s.id}
                  onClick={() => {
                    onSelect(s);
                    setOpen(false);
                    setQuery("");
                  }}
                  className="w-full text-left px-3 py-2 hover:bg-surface-3 transition-colors"
                >
                  <div className="flex items-center gap-2 mb-0.5">
                    <span
                      className={`text-[10px] font-mono px-1.5 py-0.5 rounded border ${
                        s.status === "active"
                          ? "bg-status-success/10 text-status-success border-status-success/30"
                          : s.status === "completed"
                            ? "bg-sky-500/10 text-sky-300 border-sky-500/30"
                            : s.status === "error"
                              ? "bg-status-danger/10 text-status-danger border-status-danger/30"
                              : "bg-surface-3 text-fg-secondary border-border"
                      }`}
                    >
                      {s.status}
                    </span>
                    {s.name?.trim() && (
                      <span className="text-[11px] text-fg-secondary truncate">
                        {s.name.trim()}
                      </span>
                    )}
                    <span className="font-mono text-[11px] text-fg-secondary truncate flex-shrink-0">
                      {s.id.slice(0, 12)}…
                    </span>
                    <span className="text-[10px] text-fg-muted ml-auto flex-shrink-0">
                      {new Date(s.started_at).toLocaleString()}
                    </span>
                  </div>
                  <div className="font-mono text-[10px] text-fg-muted truncate">{s.cwd}</div>
                </button>
              ))
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// The custom Select dropdown now lives in ../components/Select (shared with the
// webhook settings form). Imported at the top of this file.

// Sentinel option value for "Custom model…". Empty string is already taken by
// the "inherit from settings" choice, so use a non-empty marker.
const MODEL_CUSTOM = "__custom__";

function ModelPicker({ value, onChange }: { value: string; onChange: (s: string) => void }) {
  const { t } = useTranslation("run");
  // "Custom" is selected when the value isn't one of our curated IDs.
  const knownIds = useMemo(() => RUN_MODEL_CHOICES.map((c) => c.id), []);
  const isCustom = value !== "" && !knownIds.includes(value);
  const [showCustom, setShowCustom] = useState(isCustom);

  // Reuse the shared Select so the Model dropdown renders identically to the
  // Permission Mode and Effort dropdowns (Tailwind + lucide popover) instead of
  // a browser-native <select>.
  const options = useMemo(
    () => [
      ...RUN_MODEL_CHOICES.map((c) => ({
        value: c.id === "" ? "" : c.id,
        label: c.id === "" ? t("fields.modelInheritLabel") : c.label,
        hint: c.hint,
      })),
      { value: MODEL_CUSTOM, label: t("fields.modelCustom") },
    ],
    [t]
  );

  const onSelect = (v: string) => {
    if (v === MODEL_CUSTOM) {
      setShowCustom(true);
      return;
    }
    setShowCustom(false);
    onChange(v);
  };

  const selectValue = showCustom || isCustom ? MODEL_CUSTOM : value;

  return (
    <div className="space-y-1.5">
      <Select<string> value={selectValue} onChange={onSelect} options={options} />
      {(showCustom || isCustom) && (
        <input
          type="text"
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={t("fields.modelCustomPlaceholder")}
          autoComplete="off"
          spellCheck={false}
          className="w-full bg-surface-2 border border-border rounded-md px-3 py-1.5 text-[11px] font-mono text-fg-primary placeholder:text-fg-muted focus:outline-none focus:border-accent/50"
        />
      )}
    </div>
  );
}
