"use client";

import * as React from "react";
import { EyeIcon, EyeOffIcon, KeyRoundIcon, MinusIcon, TriangleAlertIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { FieldDescription, FieldError } from "@/components/ui/field";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
import { cn } from "@/lib/utils";

import { ConfirmDialog } from "./confirm-dialog";

/**
 * What the API says about a stored secret. Never the secret itself: the API returns only whether one is set
 * (and, if it provides it, when and by whom it last changed). Spec G1 §10 + G4 §14.2.
 */
export type SecretStatus = { set: boolean; lastChanged?: { at: string; by: string } };

/** Trim and report what was removed, so the person knows a pasted key changed. */
export function cleanSecret(raw: string): { value: string; note?: string } {
  const value = raw.trim();
  if (value === raw) return { value };
  const lead = raw.length - raw.trimStart().length;
  const trail = raw.length - raw.trimEnd().length;
  const where = lead && trail ? "at the start and end" : lead ? "at the start" : "at the end";
  return { value, note: `Removed ${lead + trail === 1 ? "a space" : "spaces"} ${where}.` };
}

function MaskedEntry({
  id,
  name,
  label,
  value,
  onChange,
  onCancel,
  error,
  autoFocus,
  inputRef,
}: {
  id: string;
  name: string;
  label: string;
  value: string;
  onChange: (v: string) => void;
  onCancel: () => void;
  error?: string;
  autoFocus?: boolean;
  inputRef?: React.Ref<HTMLInputElement>;
}) {
  const [shown, setShown] = React.useState(false);
  return (
    <InputGroup>
      <InputGroupInput
        ref={inputRef}
        id={id}
        name={name}
        type={shown ? "text" : "password"}
        // Keeps password managers from saving or autofilling integration keys as a login.
        autoComplete="new-password"
        spellCheck={false}
        autoCapitalize="off"
        className="font-mono"
        value={value}
        autoFocus={autoFocus}
        aria-invalid={!!error}
        aria-label={label}
        onChange={(e) => onChange(e.target.value)}
        onBlur={() => setShown(false)}
        onKeyDown={(e) => {
          if (e.key === "Escape") {
            e.preventDefault();
            e.stopPropagation();
            onCancel();
          }
        }}
      />
      <InputGroupAddon align="inline-end">
        <InputGroupButton
          size="icon-xs"
          aria-label={shown ? "Hide typed value" : "Show typed value"}
          aria-pressed={shown}
          // Keep focus in the input so blur does not immediately hide it again.
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => setShown((s) => !s)}
          disabled={!value}
        >
          {shown ? <EyeOffIcon /> : <EyeIcon />}
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  );
}

function StatusLine({ status, required, requiredHint, disabled }: { status: SecretStatus; required?: boolean; requiredHint?: string; disabled?: boolean }) {
  if (status.set)
    return (
      <div className={cn("text-sm", disabled ? "text-muted-foreground" : "text-foreground")}>
        <span className="inline-flex items-center gap-1.5 font-medium">
          <KeyRoundIcon className="size-4 shrink-0" aria-hidden /> Set
        </span>
        {status.lastChanged ? (
          <span className="block text-xs text-muted-foreground">
            Last changed {status.lastChanged.at} by {status.lastChanged.by}
          </span>
        ) : null}
      </div>
    );
  return required ? (
    <div className="text-sm text-status-warning-text">
      <span className="inline-flex items-center gap-1.5 font-medium">
        <TriangleAlertIcon className="size-4 shrink-0" aria-hidden /> Not set
      </span>
      {requiredHint ? <span className="block text-xs">{requiredHint}</span> : null}
    </div>
  ) : (
    <span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
      <MinusIcon className="size-4 shrink-0" aria-hidden /> Not set
    </span>
  );
}

export type SecretFieldDef = { name: string; label: string };

/**
 * Secret input: shows "Set" / "Not set" for a stored integration secret and lets an admin replace it.
 * The stored value is never shown, copied or sent back; `onReplace` reports the new value(s) only while
 * replacing (null when not), so an untouched secret is left out of the save request.
 * Pass two `fields` for a pair that must be replaced together (consumer key + secret).
 */
export function SecretInput({
  fields,
  status,
  required,
  requiredHint,
  hint,
  readOnly,
  disabled,
  removable,
  errors,
  onReplace,
  onRemove,
}: {
  fields: SecretFieldDef[];
  status: SecretStatus;
  /** Show "Not set" as a warning: the integration does not work without it. */
  required?: boolean;
  requiredHint?: string;
  /** Shown while replacing, e.g. where to find the key and that it won't be shown again. */
  hint?: string;
  /** May view the status but not change it. */
  readOnly?: boolean;
  disabled?: boolean;
  removable?: boolean;
  /** Per field name, from the form's validation. */
  errors?: Record<string, string | undefined>;
  onReplace: (values: Record<string, string> | null) => void;
  onRemove?: () => Promise<void> | void;
}) {
  const [replacing, setReplacing] = React.useState(false);
  const [values, setValues] = React.useState<Record<string, string>>({});
  const [confirmRemove, setConfirmRemove] = React.useState(false);
  const replaceRef = React.useRef<HTMLButtonElement>(null);
  const firstInput = React.useRef<HTMLInputElement>(null);
  const id = React.useId();
  const groupLabel = fields.map((f) => f.label).join(" and ");

  const start = () => {
    setReplacing(true);
    setValues({});
    onReplace(Object.fromEntries(fields.map((f) => [f.name, ""])));
  };
  const cancel = () => {
    setReplacing(false);
    setValues({});
    onReplace(null);
    setTimeout(() => replaceRef.current?.focus(), 0); // spec: focus returns to Replace
  };
  // A successful save flips status (and replacing ends via the parent remounting or this effect).
  const lastSet = React.useRef(status);
  React.useEffect(() => {
    if (status !== lastSet.current) {
      lastSet.current = status;
      setReplacing(false);
      setValues({});
    }
  }, [status]);

  return (
    <div className="space-y-2" role="group" aria-label={`${groupLabel}, ${status.set ? "set" : "not set"}`}>
      {!replacing ? (
        <div className="flex flex-wrap items-start justify-between gap-3">
          <div className="min-w-0">
            <p className="mb-1 text-sm font-medium">{groupLabel}</p>
            <StatusLine status={status} required={required} requiredHint={requiredHint} disabled={disabled} />
          </div>
          {!readOnly && !disabled ? (
            <div className="flex items-center gap-1">
              <Button ref={replaceRef} variant="outline" size="sm" onClick={start} aria-label={`${status.set ? "Replace" : "Add"} ${groupLabel}`}>
                {status.set ? "Replace" : "Add"}
              </Button>
              {removable && status.set ? (
                <Button variant="ghost" size="sm" className="text-status-error-text hover:text-status-error-text" onClick={() => setConfirmRemove(true)} aria-label={`Remove ${groupLabel}`}>
                  Remove
                </Button>
              ) : null}
            </div>
          ) : null}
        </div>
      ) : (
        <div className="space-y-3 rounded-lg border border-border p-3">
          {fields.map((f, i) => (
            <div key={f.name} className="space-y-1.5">
              <label htmlFor={`${id}-${f.name}`} className="text-sm font-medium">
                {f.label}
                {fields.length > 1 ? <span className="font-normal text-muted-foreground"> (new)</span> : null}
              </label>
              <MaskedEntry
                id={`${id}-${f.name}`}
                name={`integration-${f.name}`}
                label={`New ${f.label}`}
                value={values[f.name] ?? ""}
                autoFocus={i === 0}
                inputRef={i === 0 ? firstInput : undefined}
                error={errors?.[f.name]}
                onCancel={cancel}
                onChange={(v) => {
                  const next = { ...values, [f.name]: v };
                  setValues(next);
                  onReplace(Object.fromEntries(fields.map((x) => [x.name, next[x.name] ?? ""])));
                }}
              />
              {errors?.[f.name] ? <FieldError>{errors[f.name]}</FieldError> : null}
            </div>
          ))}
          <div className="flex flex-wrap items-center justify-between gap-2">
            <FieldDescription className="max-w-md">{hint ?? "It won't be shown again after you save."}</FieldDescription>
            <Button variant="ghost" size="sm" onClick={cancel}>
              Cancel
            </Button>
          </div>
        </div>
      )}

      {removable ? (
        <ConfirmDialog
          open={confirmRemove}
          onOpenChange={setConfirmRemove}
          level="A"
          title={`Remove the ${groupLabel}?`}
          consequences={["The integration stops working until a new one is added.", "The stored value is deleted and can't be recovered."]}
          actionLabel="Remove"
          busyLabel="Removing…"
          onConfirm={async () => {
            await onRemove?.();
          }}
        />
      ) : null}
    </div>
  );
}
