// ── <PaymentFields /> composite ─────────────────────────────────
//
// Convenience component that renders the full Design-5 layout (card-first +
// inline GPay button + status strip + trust footer + gpay_ready state).
// Hosts that want full control can use `usePaymentFields` directly and render
// their own JSX.
//
// Renders fine in either a normal React tree (WC Blocks) or inside a Shadow
// DOM (web-component layer renders this via createRoot(shadowRoot)).
//
// For lifecycle-callback notifications (onTokenized / onError / onThreeDsRequired),
// wire them on the `config` you pass in — those fire from the machine directly,
// so they work regardless of which layout you render.

import { useEffect, useId, useMemo, useRef, useTransition } from 'react';
import type {
  FieldKey,
  MountTargets,
  PaymentFieldsConfig,
  PaymentFieldsError,
  PaymentFieldsMachine,
  PaymentFieldsState,
} from '../core/index';
import { PAYMENT_FIELDS_CSS } from './styles';
import { usePaymentFields, type UsePaymentFieldsReturn } from './use-payment-fields';

export interface PaymentFieldsProps {
  /** Config to construct a machine. Exactly one of `config` or `machine` must be set. */
  readonly config?: PaymentFieldsConfig;
  /** Existing machine to render against (used by the web-component layer). Exactly one of `config` or `machine` must be set. */
  readonly machine?: PaymentFieldsMachine;
  /** URL for the trust-footer lock icon (e.g., `.../site/img/lock.svg`). */
  readonly lockIconSrc: string;
  /** URL for the trust-footer logos PNG (180×40 in the production asset). */
  readonly trustLogosSrc: string;
  /** Trust-footer wording. Defaults to "Payment secured by" — preserved verbatim from the WP plugin. */
  readonly trustText?: string;
  /**
   * Full-control render-prop. When provided, the default layout is skipped and
   * you render everything yourself using the returned API. Useful when the
   * host owns the layout (e.g., WC Blocks Content component).
   */
  readonly children?: (api: UsePaymentFieldsReturn) => React.ReactNode;
}

export function PaymentFields(props: PaymentFieldsProps): React.ReactElement {
  const input = props.machine ?? props.config;
  if (!input) {
    throw new Error('PaymentFields requires either a `config` or a `machine` prop.');
  }
  const api = usePaymentFields(input);
  useCloverFooterDecoy();

  // Stable per-instance ID prefix for mount-point selectors. Inside Shadow DOM,
  // the selectors only need to be unique within the shadowRoot — but we prefix
  // anyway so the same component can render multiple times on one page.
  const reactId = useId();
  const idPrefix = useMemo(() => `wcp-pf-${reactId.replace(/[^a-zA-Z0-9_-]/g, '')}`, [reactId]);

  const targets = useMemo<MountTargets>(
    () => ({
      cardNumber: `#${idPrefix}-card-number`,
      cardDate: `#${idPrefix}-card-date`,
      cardCvv: `#${idPrefix}-card-cvv`,
      cardPostalCode: `#${idPrefix}-card-postal-code`,
      cardName: `#${idPrefix}-card-name`,
      cardEmail: `#${idPrefix}-card-email`,
      paymentRequestButton: `#${idPrefix}-payment-request-button`,
    }),
    [idPrefix],
  );

  // Mount once after the mount-point divs are in the DOM (first render commit).
  const mountStarted = useRef(false);
  useEffect(() => {
    if (mountStarted.current) {
      return;
    }
    mountStarted.current = true;
    api.mount(targets).catch((err: unknown) => {
      console.warn('[wcp-payment-fields] mount failed', err);
    });
  }, [api, targets]);

  if (props.children) {
    return <>{props.children(api)}</>;
  }

  return (
    <DefaultLayout
      api={api}
      idPrefix={idPrefix}
      lockIconSrc={props.lockIconSrc}
      trustLogosSrc={props.trustLogosSrc}
      trustText={props.trustText ?? 'Payment secured by'}
      googlePayEnabled={featureFlag(props, 'googlePay')}
      cardholderVisible={cardholderVisibility(props)}
    />
  );
}

/**
 * Mounts a decoy `.clover-footer` target for Clover's SDK. Clover's sdk.js looks
 * for an existing `.clover-footer`; if it finds none it injects its OWN footer at
 * the end of `<body>`, breaking the host page layout (WCP-730).
 *
 * Deliberately appended to `<body>` as a plain DOM node, NOT rendered in JSX:
 * Clover relocates the element to the body end and takes ownership of it, so a
 * React-managed node would tear React's reconciliation on later re-render/unmount
 * (WC Blocks re-renders the payment area on cart/method changes). This mirrors the
 * proven WC-classic pattern, where the decoy is a static template node React never
 * tracks. If the host already rendered one (e.g. classic's PHP template), we reuse
 * it instead of adding a duplicate. Owned by the SDK so every consumer —
 * web-component, WC Blocks, dashboard, PrestaShop — is protected for free.
 */
function useCloverFooterDecoy(): void {
  useEffect(() => {
    if (typeof document === 'undefined' || document.querySelector('.clover-footer')) {
      return;
    }
    const decoy = document.createElement('div');
    decoy.className = 'clover-footer';
    document.body.appendChild(decoy);
    return () => {
      decoy.remove();
    };
  }, []);
}

// ── Default Design-5 layout ────────────────────────────────────

interface DefaultLayoutProps {
  readonly api: UsePaymentFieldsReturn;
  readonly idPrefix: string;
  readonly lockIconSrc: string;
  readonly trustLogosSrc: string;
  readonly trustText: string;
  readonly googlePayEnabled: boolean;
  readonly cardholderVisible: boolean;
}

function DefaultLayout(props: DefaultLayoutProps) {
  const { api, idPrefix, googlePayEnabled, cardholderVisible } = props;
  const isGpayReady = api.state === 'gpay_ready';
  // When GPay is ready, the card fields collapse out entirely (max-height/opacity
  // transition). For in-progress states (tokenizing/submitting/3DS), we keep them
  // visible but dimmed so the user can still see what they entered.
  const isFormCollapsed = isGpayReady;
  const isFormDimmed = !isGpayReady && isInProgress(api.state);
  const [isPending, startTransition] = useTransition();

  const onUseCard = () => {
    startTransition(() => api.resetToIdle());
  };

  const machineConfig = api.machine.config;
  const formattedAmount = formatAmount(
    machineConfig.cartTotal,
    machineConfig.currency,
    machineConfig.locale,
  );

  return (
    <div className="wcp-pf">
      <style>{PAYMENT_FIELDS_CSS}</style>

      {!isGpayReady && (
        <StatusStrip key={api.state} state={api.state} error={api.error} />
      )}

      {isGpayReady && api.result && (
        <GpayReadyCard
          brand={api.result.card.brand}
          last4={api.result.card.last4}
          amount={formattedAmount}
          onUseCard={onUseCard}
          isPending={isPending}
        />
      )}

      <div className={`wcp-pf__form${isFormCollapsed ? ' wcp-pf__form--collapsed' : ''}${isFormDimmed ? ' wcp-pf__form--dimmed' : ''}`}>
        {cardholderVisible && (
          <div className="wcp-pf__grid-2">
            <FieldSlot api={api} fieldKey="cardName" mountId={`${idPrefix}-card-name`} />
            <FieldSlot api={api} fieldKey="cardEmail" mountId={`${idPrefix}-card-email`} />
          </div>
        )}

        <div className="wcp-pf__row">
          <FieldSlot api={api} fieldKey="cardNumber" mountId={`${idPrefix}-card-number`} />
        </div>

        <div className="wcp-pf__grid-3">
          <FieldSlot api={api} fieldKey="cardDate" mountId={`${idPrefix}-card-date`} />
          <FieldSlot api={api} fieldKey="cardCvv" mountId={`${idPrefix}-card-cvv`} />
          <FieldSlot api={api} fieldKey="cardPostalCode" mountId={`${idPrefix}-card-postal-code`} />
        </div>
      </div>

      {googlePayEnabled && !isGpayReady && (
        <div className="wcp-pf__gpay-block">
          <div className="wcp-pf__gpay-label">Or pay instantly</div>
          <div className="wcp-pf__gpay-mount" id={`${idPrefix}-payment-request-button`} />
        </div>
      )}

      <TrustFooter
        lockIconSrc={props.lockIconSrc}
        trustLogosSrc={props.trustLogosSrc}
        trustText={props.trustText}
      />
    </div>
  );
}

// ── Sub-components ─────────────────────────────────────────────

function StatusStrip({ state, error }: { state: PaymentFieldsState; error?: PaymentFieldsError }) {
  const message = stateMessage(state, error);
  if (!message) {
    return null;
  }
  return (
    <div
      className={`wcp-pf__strip wcp-pf__strip--${message.tone}`}
      role={message.tone === 'error' ? 'alert' : 'status'}
      aria-live={message.tone === 'error' ? undefined : 'polite'}
    >
      {message.icon}
      <span>{message.text}</span>
    </div>
  );
}

function GpayReadyCard(props: {
  brand: string;
  last4: string;
  amount: string;
  onUseCard: () => void;
  isPending: boolean;
}) {
  return (
    <div className="wcp-pf__gpay-card" role="status">
      <div className="wcp-pf__gpay-card-head">
        <span className="wcp-pf__gpay-card-check" aria-hidden="true">
          <Checkmark />
        </span>
        <div className="wcp-pf__gpay-card-head-text">
          <div className="wcp-pf__gpay-card-title">Google Pay is ready.</div>
          <div className="wcp-pf__gpay-card-sub">
            Your payment is queued and runs when you submit.
          </div>
        </div>
      </div>
      <div className="wcp-pf__gpay-card-detail">
        <div className="wcp-pf__gpay-card-amount">{props.amount}</div>
        <div className="wcp-pf__gpay-card-info">
          <span className="wcp-pf__gpay-card-info-row">
            via <b>Google Pay</b>
          </span>
          <span className="wcp-pf__gpay-card-info-row">
            {props.brand} •••• <b>{props.last4}</b>
          </span>
        </div>
      </div>
      <p className="wcp-pf__gpay-card-cta">
        Click <b>Place Order</b> below to confirm.
      </p>
      <button
        type="button"
        className="wcp-pf__gpay-card-switch"
        onClick={props.onUseCard}
        disabled={props.isPending}
      >
        Pay with card instead
      </button>
    </div>
  );
}

function formatAmount(cents: number, currency: string, locale: string): string {
  try {
    return new Intl.NumberFormat(locale, {
      style: 'currency',
      currency,
    }).format(cents / 100);
  } catch {
    return `${(cents / 100).toFixed(2)} ${currency}`;
  }
}

function TrustFooter(props: { lockIconSrc: string; trustLogosSrc: string; trustText: string }) {
  return (
    <div className="wcp-pf__trust">
      <div className="wcp-pf__trust-lock">
        <img src={props.lockIconSrc} alt="" width={20} height={20} />
      </div>
      <div className="wcp-pf__trust-display">
        <span className="wcp-pf__trust-text">{props.trustText}</span>
        <img
          className="wcp-pf__trust-logos"
          src={props.trustLogosSrc}
          alt="Secured by Clover & WeeConnectPay"
          width={180}
          height={40}
        />
      </div>
    </div>
  );
}

// ── helpers ─────────────────────────────────────────────────────

function cardholderVisibility(props: PaymentFieldsProps): boolean {
  const features = props.config?.features ?? props.machine?.config?.features;
  const setting = features?.cardholderFields ?? 'auto';
  if (setting === 'always') return true;
  if (setting === 'hidden') return false;
  return features?.threeDSecure === true;
}

function featureFlag(props: PaymentFieldsProps, flag: 'googlePay' | 'threeDSecure'): boolean {
  const features = props.config?.features ?? props.machine?.config?.features;
  return features?.[flag] === true;
}

/**
 * Renders a single Clover-iframe mount point with deferred error text and
 * positive (valid) confirmation.
 *
 * Wrapped in a `wcp-pf__field-slot` div so it can sit in a row/grid cell as a
 * single child while the error text flows underneath the field without
 * disrupting sibling field alignment (parent grids use `align-items: start`).
 *
 * Error visual reads `displayedError`, NOT raw `error` — so it stays neutral
 * until the user has blurred once (deferred-error rule, see ValidationMachine
 * comment block). The `--valid` green check, on the other hand, reacts
 * immediately to `touched + no error` — positive feedback is never deferred.
 */
function FieldSlot(props: {
  readonly api: UsePaymentFieldsReturn;
  readonly fieldKey: FieldKey;
  readonly mountId: string;
}): React.ReactElement {
  const field = props.api.validation.fields[props.fieldKey];
  const displayedError = field?.displayedError;
  // Clover doesn't distinguish empty from valid for cardName/cardEmail (see
  // ValidationMachine comment), so we require at least one change before
  // calling them valid — otherwise focus+blur with an empty field would draw
  // a green check on nothing.
  const requiresChangeForValid = props.fieldKey === 'cardName' || props.fieldKey === 'cardEmail';
  const valid =
    !!field?.touched &&
    !field?.error &&
    (!requiresChangeForValid || !!field?.hadChangeEver);
  const fieldClass = displayedError
    ? 'wcp-pf__field wcp-pf__field--error'
    : valid
      ? 'wcp-pf__field wcp-pf__field--valid'
      : 'wcp-pf__field';
  const showCheck = valid && props.fieldKey !== 'cardNumber';
  return (
    <div className="wcp-pf__field-slot">
      <div className={fieldClass} id={props.mountId}>
        {showCheck && (
          <span className="wcp-pf__field-check" aria-hidden="true">
            <Checkmark />
          </span>
        )}
      </div>
      {displayedError && (
        <div className="wcp-pf__error-text" role="alert">
          <ErrorIcon />
          <span>{displayedError}</span>
        </div>
      )}
    </div>
  );
}

function isInProgress(state: PaymentFieldsState): boolean {
  return (
    state === 'gpay_opening' ||
    state === 'tokenizing' ||
    state === 'submitting' ||
    state === 'threeds_method' ||
    state === 'threeds_challenge' ||
    state === 'finalizing'
  );
}

interface StripMessage {
  readonly text: string;
  readonly tone: 'info' | 'warning' | 'success' | 'error';
  readonly icon: React.ReactNode;
}

function stateMessage(state: PaymentFieldsState, error?: PaymentFieldsError): StripMessage | null {
  switch (state) {
    case 'idle':
    case 'gpay_cancelled':
      return null;
    case 'gpay_opening':
      return { text: 'Opening Google Pay…', tone: 'info', icon: <Spinner /> };
    case 'gpay_ready':
      return {
        text: 'Google Pay ready — place your order to confirm.',
        tone: 'success',
        icon: <Checkmark />,
      };
    case 'tokenizing':
      return { text: 'Securing card details…', tone: 'info', icon: <Spinner /> };
    case 'submitting':
      return { text: 'Sending payment…', tone: 'info', icon: <Spinner /> };
    case 'threeds_method':
      return { text: 'Preparing bank verification…', tone: 'info', icon: <Spinner /> };
    case 'threeds_challenge':
      return { text: 'Complete verification with your bank →', tone: 'warning', icon: <Shield /> };
    case 'finalizing':
      return { text: 'Confirming payment…', tone: 'info', icon: <Spinner /> };
    case 'done':
      return { text: 'Payment confirmed.', tone: 'success', icon: <Checkmark /> };
    case 'error':
      return {
        text: error?.message ?? 'Something went wrong. Please try again.',
        tone: 'error',
        icon: <ErrorIcon />,
      };
  }
}

// ── icons ───────────────────────────────────────────────────────

function Spinner() {
  return <span className="wcp-pf__spinner" aria-hidden="true" />;
}
function Checkmark() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <polyline points="20 6 9 17 4 12" />
    </svg>
  );
}
function Shield() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
    </svg>
  );
}
function ErrorIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="10" />
      <line x1="12" y1="8" x2="12" y2="12" />
      <line x1="12" y1="16" x2="12.01" y2="16" />
    </svg>
  );
}
