import { useMemo, useState } from "react"
import { Trash2, X } from "lucide-react"
import { SecretsEditor } from "@/components/SecretsEditor"
import { ConfirmModal } from "@/components/ConfirmModal"

const INPUT =
  "bg-black/30 border border-[var(--border)] rounded px-3 py-1.5 text-sm focus:outline-none focus:border-[var(--primary)]"

export function Field({
  label,
  children,
  hint,
}: {
  label: string
  children: React.ReactNode
  hint?: string
}) {
  return (
    <div className="flex items-start gap-4">
      <label className="w-24 sm:w-32 shrink-0 text-sm font-medium pt-1.5">{label}</label>
      <div className="flex-1 min-w-0">
        {children}
        {hint && <p className="text-xs text-[var(--muted)] mt-1 leading-snug">{hint}</p>}
      </div>
    </div>
  )
}

export function TextField({
  label,
  value,
  onChange,
  placeholder,
  mono,
  width,
  hint,
}: {
  label: string
  value: string
  onChange: (v: string) => void
  placeholder?: string
  mono?: boolean
  width?: string
  hint?: string
}) {
  return (
    <Field label={label} hint={hint}>
      <input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        className={`${width ?? "w-full"} ${INPUT} ${mono ? "font-mono" : ""}`}
      />
    </Field>
  )
}

/** The public-address sub-control shown when a deployment's reach is `public`:
 * a default/custom choice. `default` uses `<name>.<public_domain>` (shown
 * read-only); `custom` reveals a text input for an exact FQDN (apex or another
 * zone) that becomes the deployment's `public_host`. */
export function PublicHostRadios({
  mode,
  onModeChange,
  value,
  onChange,
  defaultHost,
}: {
  mode: "default" | "custom"
  onModeChange: (m: "default" | "custom") => void
  value: string
  onChange: (v: string) => void
  defaultHost: string
}) {
  return (
    <div className="space-y-1.5">
      <label className="flex items-center gap-2 text-sm cursor-pointer">
        <input type="radio" checked={mode === "default"} onChange={() => onModeChange("default")} />
        <span className="font-mono text-xs text-[var(--muted)]">
          {defaultHost} <span className="opacity-60">· default</span>
        </span>
      </label>
      <label className="flex items-center gap-2 text-sm cursor-pointer">
        <input type="radio" checked={mode === "custom"} onChange={() => onModeChange("custom")} />
        <span className="text-sm">custom domain</span>
      </label>
      {mode === "custom" && (
        <div className="ml-6 space-y-1">
          <input
            value={value}
            onChange={(e) => onChange(e.target.value)}
            placeholder="example.com"
            className={`w-64 ${INPUT} font-mono`}
          />
          <p className="text-xs text-[var(--muted)] leading-snug">
            An exact hostname — an apex (example.com) or a name in another zone. The
            Cloudflare token(s) need DNS:Edit on that zone.
          </p>
        </div>
      )}
    </div>
  )
}

// A value that is *exactly* a secret ref → fully editable via SecretsEditor.
const SECRET_RE = /^\$\{secret:([^}]+)\}$/
// A secret ref embedded anywhere in a value (e.g. `neo4j/${secret:PW}`). These
// are composite literals, so we surface them read-only rather than letting
// merged() rewrite the value and clobber the surrounding text.
const EMBEDDED_SECRET_RE = /\$\{secret:([^}]+)\}/g

/** Hook for editing a run env that mixes plain vars and `${secret:NAME}` refs.
 * Returns the editor element plus a `merged()` that reconstitutes the env. */
export function useEnvSecrets(initial: Record<string, string>) {
  const { plain, secretRefs, embeddedRefs } = useMemo(() => {
    const p: Record<string, string> = {}
    const s: Record<string, string> = {}
    const e: { envKey: string; secretName: string }[] = []
    for (const [k, v] of Object.entries(initial)) {
      const m = SECRET_RE.exec(v)
      if (m) {
        s[k] = m[1]
      } else {
        // Composite values stay in `plain` so their literal text round-trips
        // untouched; any embedded secret names are surfaced read-only below.
        p[k] = v
        for (const em of v.matchAll(EMBEDDED_SECRET_RE)) {
          e.push({ envKey: k, secretName: em[1] })
        }
      }
    }
    return { plain: p, secretRefs: s, embeddedRefs: e }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const [env, setEnv] = useState<Record<string, string>>(plain)
  const [secrets, setSecrets] = useState<Record<string, string>>(secretRefs)

  // Inline "add variable" row (replaces a native prompt). `adding` toggles it;
  // the key is committed only when non-blank and not already an env/secret name.
  const [adding, setAdding] = useState(false)
  const [newKey, setNewKey] = useState("")
  const [newVal, setNewVal] = useState("")
  const trimmedKey = newKey.trim()
  const duplicate = trimmedKey !== "" && (trimmedKey in env || trimmedKey in secrets)
  const canCommit = trimmedKey !== "" && !duplicate

  const cancelNew = () => {
    setAdding(false)
    setNewKey("")
    setNewVal("")
  }
  const commitNew = () => {
    if (!canCommit) return
    setEnv((p) => ({ ...p, [trimmedKey]: newVal }))
    cancelNew()
  }

  const merged = (): Record<string, string> => {
    const out: Record<string, string> = { ...env }
    for (const [k, name] of Object.entries(secrets)) out[k] = `\${secret:${name}}`
    return out
  }

  const element = (
    <div className="space-y-4">
      <Field label="Environment">
        <div className="space-y-2">
          <p className="text-xs text-[var(--muted)]">
            Use <code className="font-mono">${"{port}"}</code>,{" "}
            <code className="font-mono">${"{data_dir}"}</code>,{" "}
            <code className="font-mono">${"{name}"}</code>,{" "}
            <code className="font-mono">${"{public_url}"}</code> for castle's computed values,
            and <code className="font-mono">${"{secret:NAME}"}</code> for secrets.
          </p>
          {Object.entries(env).map(([key, val]) => (
            <div key={key} className="flex items-center gap-2">
              <input
                value={key}
                readOnly
                className={`w-24 sm:w-56 min-w-0 ${INPUT} text-xs text-[var(--muted)]`}
              />
              <span className="text-[var(--muted)] shrink-0">=</span>
              <input
                value={val}
                onChange={(e) => setEnv((p) => ({ ...p, [key]: e.target.value }))}
                className={`flex-1 min-w-0 ${INPUT} text-xs font-mono`}
              />
              <button
                onClick={() =>
                  setEnv((p) => {
                    const n = { ...p }
                    delete n[key]
                    return n
                  })
                }
                className="text-red-400 hover:text-red-300 p-0.5 shrink-0"
                title="Remove"
              >
                <Trash2 size={12} />
              </button>
            </div>
          ))}
          {adding ? (
            <div className="space-y-1">
              <div className="flex items-center gap-2">
                <input
                  autoFocus
                  value={newKey}
                  onChange={(e) => setNewKey(e.target.value)}
                  onKeyDown={(e) => {
                    if (e.key === "Enter") commitNew()
                    if (e.key === "Escape") cancelNew()
                  }}
                  placeholder="VARIABLE_NAME"
                  className={`w-24 sm:w-56 min-w-0 ${INPUT} text-xs font-mono`}
                />
                <span className="text-[var(--muted)] shrink-0">=</span>
                <input
                  value={newVal}
                  onChange={(e) => setNewVal(e.target.value)}
                  onKeyDown={(e) => {
                    if (e.key === "Enter") commitNew()
                    if (e.key === "Escape") cancelNew()
                  }}
                  placeholder="value"
                  className={`flex-1 min-w-0 ${INPUT} text-xs font-mono`}
                />
                <button
                  onClick={commitNew}
                  disabled={!canCommit}
                  className="px-2 py-1 text-xs rounded bg-blue-700 hover:bg-blue-600 text-white transition-colors disabled:opacity-40 shrink-0"
                >
                  Add
                </button>
                <button
                  onClick={cancelNew}
                  className="text-[var(--muted)] hover:text-[var(--foreground)] p-0.5 shrink-0"
                  title="Cancel"
                >
                  <X size={12} />
                </button>
              </div>
              {duplicate && (
                <p className="text-xs text-amber-400">
                  <span className="font-mono">{trimmedKey}</span> already exists.
                </p>
              )}
            </div>
          ) : (
            <button
              onClick={() => setAdding(true)}
              className="text-xs text-[var(--primary)] hover:underline"
            >
              + Add variable
            </button>
          )}
        </div>
      </Field>
      <Field label="Secrets">
        <SecretsEditor secrets={secrets} onSecretsChange={setSecrets} />
        {embeddedRefs.length > 0 && (
          <div className="mt-2 space-y-1">
            {embeddedRefs.map(({ envKey, secretName }) => (
              <p
                key={`${envKey}:${secretName}`}
                className="text-xs font-mono text-[var(--muted)]"
              >
                <span className="text-[var(--foreground)]">{secretName}</span> — embedded in{" "}
                {envKey} (read-only)
              </p>
            ))}
          </div>
        )}
      </Field>
    </div>
  )

  return { element, merged }
}

/** A deployment-to-deployment requirement as stored in `requires`. `kind` defaults
 * to "deployment" on the backend, so the editor only surfaces ref + optional bind. */
export interface Requirement {
  kind?: string
  ref: string
  bind?: string
}

/** Hook for editing a deployment's `requires` (service→service dependencies).
 * Returns the editor element plus a `value()` that yields the requirement list to
 * save (empty entries dropped; bind omitted when blank). */
export function useRequires(initial: Requirement[]) {
  const [reqs, setReqs] = useState<Requirement[]>(() =>
    (initial ?? []).map((r) => ({ ref: r.ref ?? "", bind: r.bind ?? "" })),
  )

  const value = (): Requirement[] =>
    reqs
      .filter((r) => r.ref.trim())
      .map((r) =>
        r.bind?.trim()
          ? { ref: r.ref.trim(), bind: r.bind.trim() }
          : { ref: r.ref.trim() },
      )

  const element = (
    <Field
      label="Requires"
      hint="Other deployments this one depends on (drawn as edges on the graph). Add an env var to project the target's URL into the environment (e.g. API_URL → https://target.<domain>)."
    >
      <div className="space-y-2">
        {reqs.map((r, i) => (
          <div key={i} className="flex items-center gap-2">
            <input
              value={r.ref}
              onChange={(e) =>
                setReqs((p) => p.map((x, j) => (j === i ? { ...x, ref: e.target.value } : x)))
              }
              placeholder="deployment name"
              className={`w-32 sm:w-48 min-w-0 ${INPUT} text-xs font-mono`}
            />
            <span className="text-[var(--muted)] shrink-0 text-xs">→</span>
            <input
              value={r.bind ?? ""}
              onChange={(e) =>
                setReqs((p) => p.map((x, j) => (j === i ? { ...x, bind: e.target.value } : x)))
              }
              placeholder="ENV_VAR (optional)"
              className={`flex-1 min-w-0 ${INPUT} text-xs font-mono`}
            />
            <button
              onClick={() => setReqs((p) => p.filter((_, j) => j !== i))}
              className="text-red-400 hover:text-red-300 p-0.5 shrink-0"
              title="Remove"
            >
              <Trash2 size={12} />
            </button>
          </div>
        ))}
        <button
          onClick={() => setReqs((p) => [...p, { ref: "", bind: "" }])}
          className="text-xs text-[var(--primary)] hover:underline"
        >
          + Add dependency
        </button>
      </div>
    </Field>
  )

  return { element, value }
}

/** Save/Delete footer shared by the typed config forms. */
export function FormFooter({
  saving,
  saved,
  onSave,
  onDelete,
  deleteLabel,
  confirmMessage,
  deleteBlocked,
}: {
  saving: boolean
  saved: boolean
  onSave: () => void
  onDelete?: () => void
  deleteLabel: string
  confirmMessage?: string
  /** When set, removal is disallowed and this reason is shown instead of the button. */
  deleteBlocked?: string
}) {
  const [confirmOpen, setConfirmOpen] = useState(false)
  return (
    <div className="flex items-center justify-between pt-3 border-t border-[var(--border)]">
      {deleteBlocked ? (
        <span className="text-xs text-amber-400">{deleteBlocked}</span>
      ) : onDelete ? (
        <button
          onClick={() => setConfirmOpen(true)}
          className="flex items-center gap-1.5 text-xs text-red-400 hover:text-red-300"
        >
          <Trash2 size={12} /> {deleteLabel}
        </button>
      ) : (
        <div />
      )}
      <button
        onClick={onSave}
        disabled={saving}
        className="px-3 py-1.5 text-sm rounded bg-blue-700 hover:bg-blue-600 text-white transition-colors disabled:opacity-40"
      >
        {saving ? "Saving…" : saved ? "Saved" : "Save"}
      </button>
      {onDelete && (
        <ConfirmModal
          open={confirmOpen}
          title={deleteLabel}
          body={confirmMessage}
          confirmLabel={deleteLabel}
          danger
          onConfirm={() => {
            setConfirmOpen(false)
            onDelete()
          }}
          onCancel={() => setConfirmOpen(false)}
        />
      )}
    </div>
  )
}
