// ---------------------------------------------------------------------------
// Auth placements — "where does the credential go".
//
// Custom auth is concrete, not abstract: a method declares one or more
// PLACEMENTS. A placement says the carrier (HTTP header or query param), the
// name, an optional literal prefix (e.g. `Bearer `), and the input VARIABLE it
// renders from. A single-input method's placements all share the `token`
// variable (one secret, possibly in several spots); a multi-input method (e.g.
// Datadog's two keys) gives each placement its own variable, so an account fills
// one value per distinct variable.
//
// Serialize/parse to/from a plugin's wire auth-template (e.g. OpenAPI's
// `APIKeyAuthentication`) is plugin-specific and lives with the owning plugin —
// this module stays plugin-agnostic and only owns the generic placement shape
// and its presentational helpers.
// ---------------------------------------------------------------------------

import { AuthTemplateSlug } from "@executor-js/sdk/shared";
import type { AuthMethodDescriptor } from "@executor-js/sdk/shared";

export type Carrier = "header" | "query" | "env";

export interface Placement {
  readonly carrier: Carrier;
  /** Header name (e.g. `Authorization`) or query-param name (e.g. `api_key`). */
  readonly name: string;
  /** Literal prefix prepended to the secret, e.g. `Bearer `. May be empty. */
  readonly prefix: string;
  /** The input variable this placement renders from. Absent means "derive on
   *  serialize" — a lone input becomes the canonical `token`, multiple inputs
   *  each get a distinct variable. Two placements sharing a variable share one
   *  value. */
  readonly variable?: string;
  /** Set when the placement renders this exact value instead of a credential
   *  (a static header/param the method carries). Carried through edits; the
   *  editor doesn't create these. */
  readonly literal?: string;
}

/** A fresh, empty header placement — the default first row in an editor. */
export const emptyPlacement = (): Placement => ({ carrier: "header", name: "", prefix: "" });

/** Parses a registry header pattern — `"Authorization: Bearer {token}"` — into
 *  a placement. The literal text between the colon and the `{variable}` is the
 *  prefix, and its ABSENCE is load-bearing: `"Authorization: {api_key}"` is how
 *  the registry says Linear's personal keys take no Bearer prefix. */
export function placementFromHeaderPattern(pattern: string): Placement | null {
  const match = /^([A-Za-z0-9-]+):\s*(.*)$/.exec(pattern.trim());
  if (!match) return null;
  const rest = match[2] ?? "";
  const brace = rest.indexOf("{");
  return {
    carrier: "header",
    name: match[1] ?? "",
    prefix: brace >= 0 ? rest.slice(0, brace) : "",
  };
}

/** What an auth method is, presentationally. `kind` drives the credential UI:
 *  `oauth` shows a Connect button, `none` creates a connection with no
 *  credential inputs, and apikey/custom fill secrets across `placements`.
 *  `source` distinguishes integration-declared ("spec") methods from
 *  user-defined ("custom") ones. `template` is the auth-template slug the
 *  method applies a connection through. */
export type AuthMethodKind = "oauth" | "apikey" | "none";

/** Provider OAuth endpoints/scopes an `oauth` method declares, used to pre-fill
 *  the client-registration form so the user only pastes their client id/secret.
 *  Present only when `kind === "oauth"`; absent for credential methods. */
export interface AuthMethodOAuth {
  readonly authorizationUrl?: string;
  readonly tokenUrl?: string;
  readonly resource?: string | null;
  readonly scopes?: readonly string[];
  /** RFC 7591 registration endpoint, when the provider advertises Dynamic
   *  Client Registration. Lets the form offer a one-click "Register
   *  automatically" path that needs no pasted client id/secret. */
  readonly registrationEndpoint?: string;
  /** For probe-at-connect providers (MCP): the endpoint to discover OAuth
   *  metadata from at connect time. When present, the connect flow probes this
   *  URL to resolve the authorize/token/registration endpoints live rather than
   *  relying on pre-resolved URLs. */
  readonly discoveryUrl?: string;
  /** True when the integration is known to support RFC 7591 dynamic client
   *  registration. Drives the transparent auto-register connect flow (probe →
   *  register → start, with no app picker). */
  readonly supportsDynamicRegistration?: boolean;
  /** True when the authorization server supports OAuth Client ID Metadata
   *  Document. The connect flow can create a public local client whose
   *  `client_id` is this host's metadata document URL, with no provider-side app
   *  registration. */
  readonly supportsClientIdMetadataDocument?: boolean;
}

export interface AuthMethod {
  readonly id: string;
  readonly label: string;
  readonly kind: AuthMethodKind;
  readonly source: "spec" | "custom";
  readonly template: AuthTemplateSlug;
  readonly placements: readonly Placement[];
  /** Declared OAuth endpoints/scopes (only for `kind === "oauth"`). */
  readonly oauth?: AuthMethodOAuth;
}

/** Short human label for a placement: "Authorization header" / "api_key query
 *  param". Falls back to a generic noun when the name is blank. */
export function placementLabel(placement: Placement): string {
  if (placement.carrier === "header") {
    return `${placement.name || "Authorization"} header`;
  }
  if (placement.carrier === "env") {
    return `${placement.name || "TOKEN"} env var`;
  }
  return `${placement.name || "api_key"} query param`;
}

// ---------------------------------------------------------------------------
// PlacementLine — renders `Authorization: Bearer ••••••` / `?api_key=••••••`.
// The secret dots are accented; the prefix is faint; the whole line is mono.
// ---------------------------------------------------------------------------

export function PlacementLine(props: { readonly placement: Placement; readonly masked?: boolean }) {
  const { placement, masked = true } = props;
  const lead =
    placement.carrier === "header"
      ? `${placement.name || "Authorization"}: `
      : placement.carrier === "env"
        ? `${placement.name || "TOKEN"}=`
        : `?${placement.name || "api_key"}=`;
  // Plain inline (not inline-flex): flex trims the whitespace at the edges of
  // each child, which would drop the space after "Authorization:" and the
  // trailing space carried by a prefix like "Bearer ", rendering
  // "Authorization:Bearer••••••". whitespace-pre-wrap keeps those spaces while
  // still allowing the line to wrap. wrap-anywhere lets an unbroken value (a
  // long API key pasted as the prefix) break mid-token — without it the line's
  // min-content width is the whole key and it stretches the dialog off-screen.
  return (
    <span className="wrap-anywhere whitespace-pre-wrap font-mono text-xs text-muted-foreground">
      {lead}
      {placement.prefix ? (
        <span className="text-muted-foreground/60">{placement.prefix}</span>
      ) : null}
      <span className="tracking-widest text-primary">{masked ? "••••••" : "value"}</span>
    </span>
  );
}

// ---------------------------------------------------------------------------
// Catalog-descriptor → client AuthMethod conversion.
//
// The integration catalog response carries each integration's declared auth
// methods as plugin-agnostic `AuthMethodDescriptor[]` (derived server-side from
// the owning plugin's opaque config). This converts that wire shape into the
// presentational `AuthMethod[]` the hub renders — the single, plugin-agnostic
// home for the mapping so both the detail page and the add-account modal share
// it. `none` methods are kept: they create a named connection without
// credential inputs.
// ---------------------------------------------------------------------------

const DEFAULT_PLACEMENTS: readonly Placement[] = [
  { carrier: "header", name: "Authorization", prefix: "" },
];

/** Convert one catalog descriptor into a client `AuthMethod`. */
function authMethodFromDescriptor(descriptor: AuthMethodDescriptor): AuthMethod {
  const template = AuthTemplateSlug.make(descriptor.template);
  if (descriptor.kind === "none") {
    return {
      id: descriptor.id,
      label: descriptor.label,
      kind: "none",
      source: "spec",
      template,
      placements: [],
    };
  }
  if (descriptor.kind === "oauth") {
    const oauth = descriptor.oauth;
    return {
      id: descriptor.id,
      label: descriptor.label,
      kind: "oauth",
      source: "spec",
      template,
      placements: [],
      oauth: {
        authorizationUrl: oauth?.authorizationUrl,
        tokenUrl: oauth?.tokenUrl,
        resource: oauth?.resource ?? null,
        scopes: oauth?.scopes,
        registrationEndpoint: oauth?.registrationEndpoint,
        discoveryUrl: oauth?.discoveryUrl,
        supportsDynamicRegistration: oauth?.supportsDynamicRegistration,
        supportsClientIdMetadataDocument: oauth?.supportsClientIdMetadataDocument,
      },
    };
  }
  // "apikey" | "header" both render as a single-secret credential method; the
  // placements carry where the value is sent (defaulting to an Authorization
  // header when the plugin declares none).
  const placements: readonly Placement[] =
    descriptor.placements && descriptor.placements.length > 0
      ? descriptor.placements.map(
          (placement): Placement => ({
            carrier: placement.carrier,
            name: placement.name,
            prefix: placement.prefix,
            ...(placement.variable ? { variable: placement.variable } : {}),
            ...(placement.literal !== undefined ? { literal: placement.literal } : {}),
          }),
        )
      : DEFAULT_PLACEMENTS;
  return {
    id: descriptor.id,
    label: descriptor.label,
    kind: "apikey",
    source: "spec",
    template,
    placements,
  };
}

/** Convert an integration's declared catalog descriptors into the client
 *  `AuthMethod[]` the hub renders. */
export function authMethodsFromDescriptors(
  descriptors: readonly AuthMethodDescriptor[],
): readonly AuthMethod[] {
  return descriptors.map(authMethodFromDescriptor);
}
