/**
 * First-run wizard (L5). Shown on the very first IDE launch: it offers the
 * pre-filled, selectable reference profiles (Claude/Codex — only AIs drivable in
 * structured mode, §17.6/D7) with **editable** commands, lets the user detect
 * which CLIs are installed (✓/✗), then saves the chosen profiles and closes the
 * first run.
 *
 * The candidate list is already filtered server-side (`reference_profiles` only
 * exposes selectable profiles), so the wizard renders whatever it receives.
 * Adding an arbitrary custom profile is no longer offered, since we cannot drive
 * it in structured mode.
 *
 * Pure presentation: all behaviour comes from {@link useFirstRun} (the
 * {@link ProfileGateway} port). Profile validation is the pure logic in
 * `./profile`.
 */

import type {
  AgentProfile,
  HttpChatConfig,
  LocalModelServerConfig,
  OpenCodeConfig,
} from "@/domain";
import { Button, IconButton, Input, Panel, Toolbar, cn } from "@/shared";
import {
  ModelServersPanel,
  ModelServerSelect,
  useModelServers,
} from "@/features/model-servers";
import { useFirstRun, type WizardEntry } from "./useFirstRun";
import {
  defaultHttpChatConfig,
  defaultOpenCodeConfig,
  parseArgs,
  validateProfile,
  type ProfileErrors,
} from "./profile";

/** A small caption above a control. */
function Caption({ children }: { children: React.ReactNode }) {
  return <span className="text-xs font-medium text-muted">{children}</span>;
}

/**
 * Renders the wizard when it is the first run. Calls `onDone` once the user
 * finishes (so the host can drop the wizard and show the normal UI). Returns
 * Returns `null` while loading. By default it also returns `null` once the first
 * run is done (auto-show path in {@link App}); pass `forceOpen` to render it
 * regardless — used by "Settings ▸ Configure profiles" to reopen the wizard after
 * the first run.
 */
export function FirstRunWizard({
  onDone,
  forceOpen = false,
}: {
  onDone?: () => void;
  /** Render the wizard even when it is no longer the first run. */
  forceOpen?: boolean;
}) {
  // Mode is explicit (ticket #44), driven by the entry point — never inferred
  // from `isFirstRun`: reopening from Settings (`forceOpen`) is the "edit" mode,
  // which pre-loads and pre-selects the already-configured profiles.
  const vm = useFirstRun(forceOpen ? "edit" : "firstRun");
  const modelServers = useModelServers();

  if (vm.isFirstRun === null) return null;
  if (!forceOpen && vm.isFirstRun === false) return null;

  async function finish() {
    await vm.finish();
    onDone?.();
  }

  return (
    <Panel
      aria-label="first run setup"
      className="border-primary/40"
      title={
        <div className="flex flex-col">
          <h2 className="text-base font-semibold text-content">Welcome to IdeA</h2>
          <p className="text-sm text-muted">
            Choose which AI CLIs to configure. Commands are pre-filled and
            editable.
          </p>
        </div>
      }
    >
      <div className="flex flex-col gap-4">
        {vm.error && (
          <p role="alert" className="text-sm text-danger">
            {vm.error}
          </p>
        )}

        <Toolbar>
          {/* `detecting` deliberately does NOT disable this button (ticket #28):
              detection is best-effort and may never answer. */}
          <Button
            onClick={() => void vm.detect()}
            disabled={vm.busy}
            className="whitespace-nowrap"
          >
            Detect installed CLIs
          </Button>
          {vm.detecting && (
            <span role="status" className="text-xs text-muted">
              Detecting…
            </span>
          )}
          {/* F36: declare several local OpenCode profiles. Each click clones the
              canonical `opencode-llamacpp` seed into a new, editable row. */}
          <Button
            onClick={() => void vm.addOpenCodeProfile()}
            disabled={vm.busy}
            className="whitespace-nowrap"
          >
            Add OpenCode profile
          </Button>
        </Toolbar>

        {/* F35.2 — declare/edit/delete the local llama.cpp servers an OpenCode
            profile can bind to. Sits above the profile list so a server exists
            before it is picked in the OpenCode dropdown. */}
        <ModelServersPanel vm={modelServers} />

        <ul className="flex list-none flex-col gap-3 p-0">
          {vm.entries.map((entry) => (
            <ProfileRow
              key={entry.profile.id}
              entry={entry}
              servers={modelServers.servers}
              onToggle={() => vm.toggle(entry.profile.id)}
              onChange={(p) => vm.updateProfile(entry.profile.id, p)}
              onRemove={() => vm.remove(entry.profile.id)}
              onDuplicate={
                entry.profile.structuredAdapter === "openCode"
                  ? () =>
                      vm.addOpenCodeProfile({
                        name: `${entry.profile.name} (copy)`,
                        opencode: entry.profile.opencode,
                      })
                  : undefined
              }
            />
          ))}
        </ul>

        <footer className="flex gap-2">
          <Button variant="primary" onClick={() => void finish()} disabled={vm.busy}>
            Save and continue
          </Button>
        </footer>
      </div>
    </Panel>
  );
}

/** One editable candidate row: select, edit command/args, see availability. */
function ProfileRow({
  entry,
  servers,
  onToggle,
  onChange,
  onRemove,
  onDuplicate,
}: {
  entry: WizardEntry;
  /** Declared local model servers (F35.2), for the OpenCode binding dropdown. */
  servers: LocalModelServerConfig[];
  onToggle: () => void;
  onChange: (p: AgentProfile) => void;
  onRemove: () => void;
  /** Present only for OpenCode rows: clone this row into a new profile (F36). */
  onDuplicate?: () => void;
}) {
  const { profile, selected, available } = entry;
  const errors = validateProfile(profile);
  const isOpenCode = profile.structuredAdapter === "openCode";

  return (
    <li className="flex flex-col gap-2 rounded-md border border-border bg-raised p-3">
      <div className="flex items-center gap-2">
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            checked={selected}
            onChange={onToggle}
            aria-label={`use ${profile.name}`}
            className="accent-primary"
          />
          <strong className="text-sm text-content">{profile.name}</strong>
        </label>
        <span
          aria-label={`${profile.name} availability`}
          className={cn(
            "text-xs",
            available === null
              ? "text-faint"
              : available
                ? "text-success"
                : "text-danger",
          )}
        >
          {available === null ? "—" : available ? "✓ installed" : "✗ not found"}
        </span>
        <div className="ml-auto flex items-center gap-1">
          {onDuplicate && (
            <Button
              size="sm"
              aria-label={`duplicate ${profile.name}`}
              onClick={onDuplicate}
            >
              Duplicate
            </Button>
          )}
          <IconButton
            size="sm"
            aria-label={`remove ${profile.name}`}
            onClick={onRemove}
          >
            ×
          </IconButton>
        </div>
      </div>

      {/* F36: an OpenCode profile's name is identity-neutral but user-facing, so
          it is editable per profile (several local models coexist). */}
      {isOpenCode && (
        <label className="flex flex-col gap-1">
          <Caption>Name</Caption>
          <Input
            aria-label={`${profile.name} name`}
            value={profile.name}
            invalid={Boolean(errors.name)}
            onChange={(e) => onChange({ ...profile, name: e.target.value })}
          />
          {errors.name && <small className="text-xs text-danger">{errors.name}</small>}
        </label>
      )}

      <label className="flex flex-col gap-1">
        <Caption>Command</Caption>
        <Input
          aria-label={`${profile.name} command`}
          value={profile.command}
          invalid={Boolean(errors.command)}
          onChange={(e) => onChange({ ...profile, command: e.target.value })}
        />
        {errors.command && <small className="text-xs text-danger">{errors.command}</small>}
      </label>

      <label className="flex flex-col gap-1">
        <Caption>Arguments</Caption>
        <Input
          aria-label={`${profile.name} args`}
          value={profile.args.join(" ")}
          onChange={(e) => onChange({ ...profile, args: parseArgs(e.target.value) })}
        />
      </label>

      {profile.structuredAdapter === "openAiCompatible" && (
        <HttpChatFields profile={profile} errors={errors} onChange={onChange} />
      )}

      {profile.structuredAdapter === "openCode" && (
        <OpenCodeFields
          profile={profile}
          errors={errors}
          servers={servers}
          onChange={onChange}
        />
      )}
    </li>
  );
}

/**
 * The OpenCode + llama.cpp config section: the base URL of the local
 * `llama-server` (`/v1`), the model it serves, and an optional API key (the key
 * itself — a local llama.cpp usually needs none). Rendered only for a `openCode`
 * profile; edits flow back through `onChange` as a patched `opencode`.
 */
function OpenCodeFields({
  profile,
  errors,
  servers,
  onChange,
}: {
  profile: AgentProfile;
  errors: ProfileErrors;
  /** Declared local model servers (F35.2), for the binding dropdown. */
  servers: LocalModelServerConfig[];
  onChange: (p: AgentProfile) => void;
}) {
  const opencode = profile.opencode ?? defaultOpenCodeConfig();
  const patch = (next: Partial<OpenCodeConfig>) =>
    onChange({ ...profile, opencode: { ...opencode, ...next } });

  return (
    <fieldset className="mt-1 flex flex-col gap-2 rounded-md border border-border/70 p-2">
      <legend className="px-1 text-xs font-medium text-muted">
        Local model (OpenCode + llama.cpp)
      </legend>

      <label className="flex flex-col gap-1">
        <Caption>Base URL</Caption>
        <Input
          aria-label={`${profile.name} base url`}
          value={opencode.baseURL}
          placeholder="http://localhost:8080/v1"
          invalid={Boolean(errors.baseURL)}
          onChange={(e) => patch({ baseURL: e.target.value })}
        />
        {errors.baseURL && (
          <small className="text-xs text-danger">{errors.baseURL}</small>
        )}
      </label>

      <label className="flex flex-col gap-1">
        <Caption>Model</Caption>
        <Input
          aria-label={`${profile.name} model`}
          value={opencode.model}
          placeholder="qwen3-coder-30b"
          invalid={Boolean(errors.model)}
          onChange={(e) => patch({ model: e.target.value })}
        />
        {errors.model && <small className="text-xs text-danger">{errors.model}</small>}
      </label>

      <label className="flex flex-col gap-1">
        <Caption>API key (optional — a local llama.cpp needs none)</Caption>
        <Input
          aria-label={`${profile.name} api key`}
          value={opencode.apiKey ?? ""}
          placeholder="e.g. sk-no-key"
          onChange={(e) => {
            const v = e.target.value;
            patch({ apiKey: v.length === 0 ? undefined : v });
          }}
        />
      </label>

      <div className="flex flex-wrap gap-4">
        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            aria-label={`${profile.name} reasoning`}
            // Effective backend default is `true`; treat omitted as enabled.
            checked={opencode.reasoning ?? true}
            onChange={(e) => patch({ reasoning: e.target.checked })}
            className="accent-primary"
          />
          <Caption>Reasoning</Caption>
        </label>

        <label className="flex items-center gap-2">
          <input
            type="checkbox"
            aria-label={`${profile.name} attachment`}
            // Effective backend default is `false`.
            checked={opencode.attachment ?? false}
            onChange={(e) => patch({ attachment: e.target.checked })}
            className="accent-primary"
          />
          <Caption>Attachments</Caption>
        </label>
      </div>

      <label className="flex flex-col gap-1">
        <Caption>
          Local model server (bind to a managed llama.cpp server, or none)
        </Caption>
        <ModelServerSelect
          ariaLabel={`${profile.name} local model server`}
          servers={servers}
          value={opencode.localModelServerId}
          onChange={(serverId) => patch({ localModelServerId: serverId })}
        />
      </label>
    </fieldset>
  );
}

/** Parses a number field: blank ⇒ `undefined` (backend applies its default). */
function parseOptInt(raw: string): number | undefined {
  const trimmed = raw.trim();
  if (trimmed.length === 0) return undefined;
  const n = Number(trimmed);
  return Number.isFinite(n) ? n : NaN;
}

/**
 * The OpenAI-compatible (local/LAN) HTTP config section: endpoint, model, the
 * *name* of the API-key env var (never the key), and optional timeouts / tool
 * guard. Rendered only for a `openAiCompatible` profile; edits flow back through
 * `onChange` as a patched `chatHttp`.
 */
function HttpChatFields({
  profile,
  errors,
  onChange,
}: {
  profile: AgentProfile;
  errors: ProfileErrors;
  onChange: (p: AgentProfile) => void;
}) {
  const http = profile.chatHttp ?? defaultHttpChatConfig();
  const patch = (next: Partial<HttpChatConfig>) =>
    onChange({ ...profile, chatHttp: { ...http, ...next } });

  return (
    <fieldset className="mt-1 flex flex-col gap-2 rounded-md border border-border/70 p-2">
      <legend className="px-1 text-xs font-medium text-muted">
        Local / LAN model (OpenAI-compatible)
      </legend>

      <label className="flex flex-col gap-1">
        <Caption>Endpoint</Caption>
        <Input
          aria-label={`${profile.name} endpoint`}
          value={http.endpoint}
          placeholder="http://localhost:11434/v1"
          invalid={Boolean(errors.endpoint)}
          onChange={(e) => patch({ endpoint: e.target.value })}
        />
        {errors.endpoint && (
          <small className="text-xs text-danger">{errors.endpoint}</small>
        )}
      </label>

      <label className="flex flex-col gap-1">
        <Caption>Model</Caption>
        <Input
          aria-label={`${profile.name} model`}
          value={http.model}
          placeholder="qwen2.5-coder"
          invalid={Boolean(errors.model)}
          onChange={(e) => patch({ model: e.target.value })}
        />
        {errors.model && <small className="text-xs text-danger">{errors.model}</small>}
      </label>

      <label className="flex flex-col gap-1">
        <Caption>API key — environment variable name (never the key itself)</Caption>
        <Input
          aria-label={`${profile.name} api key env`}
          value={http.apiKeyEnv ?? ""}
          placeholder="e.g. OPENAI_API_KEY (variable name, not the secret)"
          invalid={Boolean(errors.apiKeyEnv)}
          onChange={(e) => {
            const v = e.target.value.trim();
            patch({ apiKeyEnv: v.length === 0 ? undefined : v });
          }}
        />
        {errors.apiKeyEnv && (
          <small className="text-xs text-danger">{errors.apiKeyEnv}</small>
        )}
      </label>

      <div className="flex flex-wrap gap-2">
        <label className="flex min-w-[8rem] flex-1 flex-col gap-1">
          <Caption>Request timeout (ms)</Caption>
          <Input
            aria-label={`${profile.name} request timeout`}
            value={http.requestTimeoutMs?.toString() ?? ""}
            inputMode="numeric"
            invalid={Boolean(errors.requestTimeoutMs)}
            onChange={(e) => patch({ requestTimeoutMs: parseOptInt(e.target.value) })}
          />
          {errors.requestTimeoutMs && (
            <small className="text-xs text-danger">{errors.requestTimeoutMs}</small>
          )}
        </label>

        <label className="flex min-w-[8rem] flex-1 flex-col gap-1">
          <Caption>Connect timeout (ms)</Caption>
          <Input
            aria-label={`${profile.name} connect timeout`}
            value={http.connectTimeoutMs?.toString() ?? ""}
            inputMode="numeric"
            invalid={Boolean(errors.connectTimeoutMs)}
            onChange={(e) => patch({ connectTimeoutMs: parseOptInt(e.target.value) })}
          />
          {errors.connectTimeoutMs && (
            <small className="text-xs text-danger">{errors.connectTimeoutMs}</small>
          )}
        </label>

        <label className="flex min-w-[8rem] flex-1 flex-col gap-1">
          <Caption>Max tool iterations</Caption>
          <Input
            aria-label={`${profile.name} max tool iterations`}
            value={http.maxToolIterations?.toString() ?? ""}
            inputMode="numeric"
            invalid={Boolean(errors.maxToolIterations)}
            onChange={(e) => patch({ maxToolIterations: parseOptInt(e.target.value) })}
          />
          {errors.maxToolIterations && (
            <small className="text-xs text-danger">{errors.maxToolIterations}</small>
          )}
        </label>
      </div>
    </fieldset>
  );
}
