import React from 'react';
import { Check, ExternalLink, X } from 'lucide-react';
import { SearchableSelect } from '@nexuscrm/salesforce-crm-standard-frontend/platform/components/ui';
import {
  CRM_CURRENCY_CODE_FIELD,
  CRM_EMAIL_MESSAGE_HTML_BODY_FIELD,
  CRM_EMAIL_MESSAGE_OBJECT,
  CRM_EMPTY_FIELD_DISPLAY,
  CRM_FIELD_DISPLAY_VALUE_SUFFIX,
  CRM_LOOKUP_FALLBACK_NAME_SUFFIXES,
  CRM_LOOKUP_ID_SUFFIX,
  CRM_LOOKUP_NAME_SUFFIX,
  CRM_LOOKUP_TYPE_SUFFIX,
  CRM_NEVER_NAVIGABLE_LOOKUP_TARGETS,
  CRM_NON_NAVIGABLE_TABLE_LOOKUP_TARGETS,
  CRM_PICKLIST_PRESENTATION_TAG_PREFIX,
  CRM_PICKLIST_PRESENTATION_TOKENS,
  CRM_UI_FIELD_INPUT_TYPES,
  CRM_UI_FIELD_RENDERER_TYPES,
  CRM_URL_VALUE_SUFFIX,
  type CrmPicklistPresentationToken,
  type CrmUiFieldInputType,
  type CrmUiFieldRendererType,
} from './ui-field-surface-policy';

export interface CrmUiFieldPicklistValue {
  value: string;
  label?: string;
  tags?: readonly string[];
}

export interface CrmUiFieldDefinition {
  name: string;
  label?: string;
  type?: string;
  picklistValues?: readonly CrmUiFieldPicklistValue[];
  referenceTo?: readonly string[];
}

export type CrmUiFieldRecord = Record<string, unknown>;

export interface CrmUiFieldRendererProps {
  field: CrmUiFieldDefinition;
  value: unknown;
  onNavigate?: (objectApiName: string, recordId: string) => void;
  record?: CrmUiFieldRecord;
  variant?: 'table' | 'detail';
}

export interface CrmUiFieldInputProps {
  field: CrmUiFieldDefinition;
  value: unknown;
  onChange: (value: unknown, selectedRecord?: CrmUiFieldRecord) => void;
  disabled?: boolean;
  placeholder?: string;
  required?: boolean;
  options?: string[];
  onKeyDown?: (event: React.KeyboardEvent) => void;
  autoFocus?: boolean;
  inputId?: string;
  ariaInvalid?: boolean;
  ariaDescribedBy?: string;
}

export interface CrmSearchableLookupProps {
  ObjectApiName: string;
  value: string;
  onChange: (value: string, selectedRecord?: CrmUiFieldRecord) => void;
  disabled?: boolean;
  placeholder?: string;
  error?: boolean;
  id?: string;
}

export interface CrmRecordLinkProps {
  objectApiName: string;
  recordId: string;
  className?: string;
  title?: string;
  onNavigate?: (objectApiName: string, recordId: string) => void;
  children: React.ReactNode;
}

export interface CrmUiFieldHost {
  SearchableLookup: React.FC<CrmSearchableLookupProps>;
  /**
   * Shared component that renders a navigable record reference as a real
   * `<a href>` (open-in-new-tab / middle-click / copy-link work; a plain
   * left-click is intercepted for in-app navigation). Host-injected so this
   * app-blind field layer does not depend on the router or route constants.
   */
  RecordLink: React.FC<CrmRecordLinkProps>;
  decodeHtmlEntities: (value: string) => string;
  /**
   * Sanitizes HTML for safe rendering via dangerouslySetInnerHTML (XSS-stripped).
   *
   * XSS is not the only threat for HTML that arrives from outside: sanitized
   * markup still loads whatever it references. `blockRemoteContent` additionally
   * neutralizes every remote fetch the markup can trigger (image/media sources,
   * srcset/poster, CSS url()), so opening a record cannot silently phone home —
   * the tracking-pixel problem. Callers rendering sender-supplied HTML must pass
   * it; trusted internal rich text does not.
   */
  sanitizeHtml: (value: string, options?: { blockRemoteContent?: boolean }) => string;
  /** Reduces HTML to its plain text content (tags stripped), for table/compact surfaces. */
  htmlToText: (value: string) => string;
  formatCurrency: (value: number, decimals?: number, currencyCode?: string) => string;
  formatDate: (value: string) => string;
  formatDateTime: (value: string) => string;
  getRecordValue: (record: CrmUiFieldRecord | undefined, fieldName: string) => unknown;
  parseMultipicklistSelections: (value: unknown) => string[];
  joinMultipicklistSelections: (values: readonly string[]) => string;
}

/**
 * A renderer bound to one (object, field) pair rather than to a field type.
 * Reserved for fields whose content has different trust properties than their
 * type implies — the type-keyed renderer cannot know that.
 */
export interface CrmUiFieldRendererOverride {
  objectApiName: string;
  fieldApiName: string;
  renderer: React.FC<CrmUiFieldRendererProps>;
}

export interface CrmUiFieldComponents {
  fieldRenderers: Record<CrmUiFieldRendererType, React.FC<CrmUiFieldRendererProps>>;
  fieldInputs: Record<CrmUiFieldInputType, React.FC<CrmUiFieldInputProps>>;
  fieldRendererOverrides: readonly CrmUiFieldRendererOverride[];
}

function getRecordCurrencyCode(host: CrmUiFieldHost, record: CrmUiFieldRecord | undefined): string | undefined {
  const currency = host.getRecordValue(record, CRM_CURRENCY_CODE_FIELD);
  if (typeof currency !== 'string') return undefined;
  const normalized = currency.trim().toUpperCase();
  return /^[A-Z]{3}$/.test(normalized) ? normalized : undefined;
}

// Muted cool-hue badge palette assigned by option ordinal (ordered for maximum
// hue distance between neighboring options). Cool hues only: warm hues are
// reserved for semantic presentation tags so any green/red/amber badge always
// carries state meaning.
const PICKLIST_BADGE_PALETTE: readonly React.CSSProperties[] = [
  { backgroundColor: '#eff6ff', color: '#1e40af', borderColor: '#bfdbfe' },
  { backgroundColor: '#f5f3ff', color: '#5b21b6', borderColor: '#ddd6fe' },
  { backgroundColor: '#ecfeff', color: '#155e75', borderColor: '#a5f3fc' },
  { backgroundColor: '#fdf4ff', color: '#86198f', borderColor: '#f5d0fe' },
  { backgroundColor: '#eef2ff', color: '#3730a3', borderColor: '#c7d2fe' },
];

// Styles for the CRM_PICKLIST_PRESENTATION_TOKENS contract. The renderer stays
// vocabulary-blind: packs own the value→token mapping. `info` deliberately
// aliases the palette's blue — informational is the default reading of cool
// hues, the tag just pins it against ordinal reshuffles.
const PICKLIST_SEMANTIC_STYLES: Record<CrmPicklistPresentationToken, React.CSSProperties> = {
  success: { backgroundColor: '#ecfdf5', color: '#065f46', borderColor: '#a7f3d0' },
  danger: { backgroundColor: '#fef2f2', color: '#991b1b', borderColor: '#fecaca' },
  warning: { backgroundColor: '#fffbeb', color: '#92400e', borderColor: '#fde68a' },
  info: PICKLIST_BADGE_PALETTE[0],
  neutral: { backgroundColor: '#f8fafc', color: '#475569', borderColor: '#e2e8f0' },
};

function getPicklistBadgeStyle(value: string, options?: readonly CrmUiFieldPicklistValue[]): React.CSSProperties {
  const index = options ? options.findIndex(pv => pv.value === value) : -1;
  const option = options?.[index];

  for (const tag of option?.tags ?? []) {
    if (!tag.startsWith(CRM_PICKLIST_PRESENTATION_TAG_PREFIX)) continue;
    const token = tag.slice(CRM_PICKLIST_PRESENTATION_TAG_PREFIX.length);
    // Membership check against the exported contract list (not a bare object
    // lookup): tag tokens are tenant-authored, so e.g. `presentation:toString`
    // must not resolve through the prototype chain.
    if ((CRM_PICKLIST_PRESENTATION_TOKENS as readonly string[]).includes(token)) {
      return PICKLIST_SEMANTIC_STYLES[token as CrmPicklistPresentationToken];
    }
  }

  if (index >= 0) return PICKLIST_BADGE_PALETTE[index % PICKLIST_BADGE_PALETTE.length];

  // Stale or ad-hoc values not present in the option list: hash into the same
  // palette so the badge stays stable and on-palette instead of an arbitrary hue.
  let hash = 0;
  for (let i = 0; i < value.length; i++) hash = value.charCodeAt(i) + ((hash << 5) - hash);
  return PICKLIST_BADGE_PALETTE[Math.abs(hash) % PICKLIST_BADGE_PALETTE.length];
}

export function createCrmUiFieldComponents(host: CrmUiFieldHost): CrmUiFieldComponents {
  const SearchableLookup = host.SearchableLookup;
  const RecordLink = host.RecordLink;

  const BooleanRenderer: React.FC<CrmUiFieldRendererProps> = ({ value }) => (
    value ? (
      <div className="flex items-center gap-1 text-emerald-600 font-medium">
        <Check size={16} /> <span className="text-xs">Yes</span>
      </div>
    ) : (
      <div className="flex items-center gap-1 text-slate-400">
        <X size={16} /> <span className="text-xs">No</span>
      </div>
    )
  );

  const MultipicklistRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, field }) => {
    const items = host.parseMultipicklistSelections(value);
    if (items.length === 0) return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;

    return (
      <div className="flex flex-wrap gap-1">
        {items.map((item) => (
          <span
            key={item}
            className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border"
            style={getPicklistBadgeStyle(item, field.picklistValues)}
          >
            {host.decodeHtmlEntities(item)}
          </span>
        ))}
      </div>
    );
  };

  const PicklistRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, field, record }) => {
    const hasOptions = field.picklistValues && field.picklistValues.length > 0;

    if (field.type === 'picklist' && !hasOptions) {
      return (
        <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800 border border-amber-200" title="Metadata Error: Picklist has no options defined">
          Broken Picklist
        </span>
      );
    }

    if (value === null || value === undefined || value === '') return null;
    const valStr = String(value);

    let displayLabel = '';
    if (record) {
      const displayVal = host.getRecordValue(record, `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`);
      if (displayVal) displayLabel = String(displayVal);
    }

    if (!displayLabel) {
      const picklistValue = field.picklistValues?.find(pv => pv.value === valStr);
      displayLabel = host.decodeHtmlEntities(picklistValue?.label || valStr);
    }

    return (
      <span
        className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border transition-all hover:scale-105"
        style={getPicklistBadgeStyle(valStr, field.picklistValues)}
      >
        {displayLabel}
      </span>
    );
  };

  const UrlRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, variant, field, record }) => {
    if (value === null || value === undefined || value === '') return null;
    const strVal = String(value);

    let href = strVal;
    if (record) {
      const urlOverride = host.getRecordValue(record, `${field.name}${CRM_URL_VALUE_SUFFIX}`);
      if (urlOverride) {
        href = String(urlOverride);
      }
    }

    if (strVal.startsWith('data:image')) {
      return (
        <div className="relative group w-fit">
          <img
            src={strVal}
            alt={field.label || field.name}
            className={`object-cover border border-slate-200 rounded-lg shadow-sm ${variant === 'table' ? 'w-10 h-10' : 'w-32 h-32'}`}
          />
        </div>
      );
    }
    return (
      <a href={href} target="_blank" rel="noreferrer" className="text-blue-600 hover:underline inline-flex items-center gap-1 truncate max-w-[200px]" onClick={(event) => event.stopPropagation()}>
        {host.decodeHtmlEntities(strVal)} <ExternalLink size={10} />
      </a>
    );
  };

  const EmailRenderer: React.FC<CrmUiFieldRendererProps> = ({ value }) => {
    if (value === null || value === undefined || value === '') return null;
    const strVal = String(value);
    return (
      <a href={`mailto:${strVal}`} className="text-blue-600 hover:underline truncate max-w-[200px]" onClick={(event) => event.stopPropagation()}>
        {strVal}
      </a>
    );
  };

  const PhoneRenderer: React.FC<CrmUiFieldRendererProps> = ({ value }) => {
    if (value === null || value === undefined || value === '') return null;
    const strVal = String(value);
    return (
      <a href={`tel:${strVal}`} className="text-blue-600 hover:underline truncate max-w-[200px]" onClick={(event) => event.stopPropagation()}>
        {strVal}
      </a>
    );
  };

  const LookupRenderer: React.FC<CrmUiFieldRendererProps> = ({ field, value, onNavigate, record, variant }) => {
    if (value === null || value === undefined || value === '') {
      return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    }

    let displayLabel = String(value);

    if (record) {
      const primaryDisplayField = `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`;
      const primaryDisplay = host.getRecordValue(record, primaryDisplayField);
      if (primaryDisplay) {
        displayLabel = String(primaryDisplay);
      } else {
        const primaryNameField = `${field.name}${CRM_LOOKUP_NAME_SUFFIX}`;
        const primaryResolved = host.getRecordValue(record, primaryNameField);
        if (primaryResolved) {
          displayLabel = String(primaryResolved);
        } else {
          for (const suffix of CRM_LOOKUP_FALLBACK_NAME_SUFFIXES) {
            const nameField = field.name.endsWith(CRM_LOOKUP_ID_SUFFIX)
              ? `${field.name.slice(0, -CRM_LOOKUP_ID_SUFFIX.length)}${suffix}`
              : `${field.name}${suffix}`;

            const resolvedName = host.getRecordValue(record, nameField);
            if (resolvedName) {
              displayLabel = String(resolvedName);
              break;
            }
          }
        }
      }
    }

    displayLabel = host.decodeHtmlEntities(displayLabel);

    if (onNavigate && field.referenceTo?.length) {
      let targetObject: string | undefined;
      if (field.referenceTo.length === 1) {
        targetObject = field.referenceTo[0];
      } else if (record) {
        const typeField = `${field.name}${CRM_LOOKUP_TYPE_SUFFIX}`;
        const resolvedType = host.getRecordValue(record, typeField);
        if (resolvedType) targetObject = String(resolvedType);
      }

      const typeHint = (Array.isArray(field.referenceTo) && field.referenceTo.length > 1 && targetObject)
        ? <span className="text-xs text-gray-400 mr-1">({targetObject})</span>
        : null;

      if (targetObject) {
        const target = targetObject.toLowerCase();
        // Metadata lookups (e.g. Record Type) never resolve to a user-facing
        // record, so they render as plain text on every surface rather than a
        // link that navigates nowhere useful. Density-only "system" lookups
        // (User/Group) keep their existing table-only plain-text treatment.
        const isNeverNavigable = CRM_NEVER_NAVIGABLE_LOOKUP_TARGETS
          .includes(target as typeof CRM_NEVER_NAVIGABLE_LOOKUP_TARGETS[number]);
        const isSystemLookup = isNeverNavigable
          || (variant === 'table'
            && CRM_NON_NAVIGABLE_TABLE_LOOKUP_TARGETS.includes(target as typeof CRM_NON_NAVIGABLE_TABLE_LOOKUP_TARGETS[number]));
        if (isSystemLookup) {
          return (
            <div className="flex min-w-0 items-center">
              {typeHint}
              <span className="truncate text-slate-700">{displayLabel}</span>
            </div>
          );
        }
        return (
          <div className="flex min-w-0 items-center">
            {typeHint}
            <RecordLink
              objectApiName={targetObject}
              recordId={String(value)}
              onNavigate={onNavigate}
              className="min-w-0 truncate text-left font-medium text-blue-600 hover:text-blue-800 hover:underline"
            >
              {displayLabel}
            </RecordLink>
          </div>
        );
      }
    }

    return <span className="text-slate-700">{displayLabel}</span>;
  };

  const PicklistInput: React.FC<CrmUiFieldInputProps> = ({ field, value, onChange, disabled, options: propOptions, onKeyDown, autoFocus, inputId, ariaInvalid }) => {
    const picklistOptions = propOptions
      ? propOptions.map(v => ({ value: v, label: v }))
      : (field.picklistValues || []).map(p => ({ value: p.value, label: p.label || p.value }));

    return (
      <div onKeyDown={onKeyDown} tabIndex={autoFocus ? 0 : undefined} ref={autoFocus ? (element) => element?.focus() : undefined}>
        <SearchableSelect
          options={picklistOptions}
          value={(value as string) || ''}
          onChange={(nextValue) => onChange(nextValue)}
          placeholder={`-- Select ${field.label || field.name} --`}
          disabled={disabled}
          id={inputId}
          error={ariaInvalid}
        />
      </div>
    );
  };

  const MultipicklistInput: React.FC<CrmUiFieldInputProps> = ({ field, value, onChange, disabled, onKeyDown, autoFocus, inputId, ariaInvalid, ariaDescribedBy }) => {
    const selected = host.parseMultipicklistSelections(value);
    const picklistOptions = (field.picklistValues || []).map(p => ({ value: p.value, label: p.label || p.value }));

    const toggle = (optValue: string) => {
      const next = selected.includes(optValue)
        ? selected.filter(v => v !== optValue)
        : [...selected, optValue];
      onChange(host.joinMultipicklistSelections(next));
    };

    if (picklistOptions.length === 0) {
      return (
        <input
          type="text"
          className={`w-full border rounded-md px-3 py-2 text-sm focus:ring-2 outline-none disabled:bg-slate-100 ${ariaInvalid ? 'border-red-400 focus:ring-red-500' : 'border-slate-300 focus:ring-blue-500'}`}
          value={host.joinMultipicklistSelections(selected)}
          onChange={(event) => onChange(event.target.value)}
          placeholder="Values separated by semicolons"
          disabled={disabled}
          onKeyDown={onKeyDown}
          autoFocus={autoFocus}
          id={inputId}
          aria-invalid={ariaInvalid || undefined}
          aria-describedby={ariaDescribedBy}
        />
      );
    }

    return (
      <div className={`border rounded-md p-2 max-h-48 overflow-y-auto space-y-1 ${ariaInvalid ? 'border-red-400' : 'border-slate-300'}`} onKeyDown={onKeyDown} tabIndex={autoFocus ? 0 : undefined} ref={autoFocus ? (element) => element?.focus() : undefined}>
        {picklistOptions.map((opt, index) => (
          <label key={opt.value} className={`flex items-center gap-2 px-2 py-1 rounded cursor-pointer hover:bg-slate-50 ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}>
            <input
              type="checkbox"
              className="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4"
              checked={selected.includes(opt.value)}
              onChange={() => toggle(opt.value)}
              disabled={disabled}
              id={index === 0 ? inputId : undefined}
              aria-invalid={ariaInvalid || undefined}
              aria-describedby={ariaDescribedBy}
            />
            <span className="text-sm text-slate-700">{opt.label}</span>
          </label>
        ))}
      </div>
    );
  };

  const ReferenceInput: React.FC<CrmUiFieldInputProps> = ({ field, value, onChange, disabled, placeholder, inputId, ariaInvalid }) => (
    <SearchableLookup
      ObjectApiName={field.referenceTo ? field.referenceTo[0] : ''}
      value={(value as string) || ''}
      onChange={(nextValue, selectedRecord) => onChange(nextValue, selectedRecord)}
      disabled={disabled}
      placeholder={placeholder || 'Search...'}
      id={inputId}
      error={ariaInvalid}
    />
  );

  const TextareaInput: React.FC<CrmUiFieldInputProps> = ({ value, onChange, disabled, required, placeholder, onKeyDown, autoFocus, inputId, ariaInvalid, ariaDescribedBy }) => (
    <textarea className={`w-full border rounded-md px-3 py-2 text-sm focus:ring-2 outline-none disabled:bg-slate-100 disabled:text-slate-500 ${ariaInvalid ? 'border-red-400 focus:ring-red-500' : 'border-slate-300 focus:ring-blue-500'}`} required={required} onChange={(event) => onChange(event.target.value)} value={(value as string) || ''} rows={3} placeholder={placeholder} disabled={disabled} onKeyDown={onKeyDown} autoFocus={autoFocus} id={inputId} aria-invalid={ariaInvalid || undefined} aria-describedby={ariaDescribedBy} />
  );

  const BooleanInput: React.FC<CrmUiFieldInputProps> = ({ field, value, onChange, disabled, onKeyDown, autoFocus, inputId, ariaInvalid, ariaDescribedBy }) => (
    <div className="flex items-center gap-2 pt-2">
      <input type="checkbox" id={inputId || `field-${field.name}`} className="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-4 w-4 disabled:opacity-50" checked={!!value} onChange={(event) => onChange(event.target.checked)} disabled={disabled} onKeyDown={onKeyDown} autoFocus={autoFocus} aria-invalid={ariaInvalid || undefined} aria-describedby={ariaDescribedBy} />
      <label htmlFor={inputId || `field-${field.name}`} className={`text-sm font-medium cursor-pointer select-none ${disabled ? 'text-slate-400' : 'text-slate-700'}`}>{field.label || field.name}</label>
    </div>
  );

  const renderDisplayValue = (value: unknown, record: CrmUiFieldRecord | undefined, field: CrmUiFieldDefinition, formatter: (value: string) => string) => {
    if (record && field) {
      const displayVal = host.getRecordValue(record, `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`);
      if (displayVal) return <span>{String(displayVal)}</span>;
    }
    if (value === null || value === undefined || value === '') return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    return <span>{formatter(String(value))}</span>;
  };

  const RichTextRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, variant }) => {
    if (value === null || value === undefined || value === '') {
      return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    }
    const raw = String(value);
    const text = host.htmlToText(raw);
    // Rich-text editors persist "empty" as markup (e.g. "<p><br></p>"); treat
    // markup with no text content as empty on every surface, matching siblings.
    if (!text) {
      return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    }

    // Only the detail surface renders sanitized HTML. Every other surface — table
    // cells, and any caller that omits variant — gets plain text so markup is
    // never injected into a compact context. Anchor clicks must not bubble to the
    // surrounding inline-edit container (which would open the editor instead of
    // navigating), mirroring the stopPropagation the plain URL renderer uses.
    if (variant === 'detail') {
      return (
        <div
          className="prose prose-sm max-w-none text-slate-700 break-words"
          onClick={(event) => {
            if ((event.target as HTMLElement).closest('a')) event.stopPropagation();
          }}
          dangerouslySetInnerHTML={{ __html: host.sanitizeHtml(raw) }}
        />
      );
    }
    return <span className="block min-w-0 truncate text-slate-700" title={text}>{text}</span>;
  };

  const TextareaRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, variant }) => {
    if (value === null || value === undefined || value === '') {
      return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    }
    const text = host.decodeHtmlEntities(String(value));
    // Long-text (plain textarea, e.g. EmailMessage.TextBody) is unbounded prose,
    // not a compact scalar. The detail surface shows the FULL body with newlines
    // preserved (whitespace-pre-wrap) instead of the single-line truncation the
    // default renderer applies; table cells and any variant-less caller stay
    // truncated so a long body never blows out a compact row. This mirrors the
    // detail/compact surface split RichTextRenderer draws for rich text.
    if (variant === 'detail') {
      return <div className="whitespace-pre-wrap break-words text-slate-700">{text}</div>;
    }
    return <span className="block min-w-0 truncate text-slate-700" title={text}>{text}</span>;
  };

  // Email HTML is the one rich-text surface authored by an outside sender, so it
  // does not go through RichTextRenderer: that renderer is built for trusted
  // internal prose and injects sanitized markup as-is, which still lets a remote
  // <img> fire on open (a tracking pixel that reports when and where a record was
  // read). Sanitizing for XSS does not address that — the fetch itself is the
  // leak. So remote references start neutralized and the reader opts in, the way
  // mail clients do. The compact surfaces stay plain text, as for rich text.
  const EmailHtmlRenderer: React.FC<CrmUiFieldRendererProps> = ({ value, variant, record }) => {
    // Consent is bound to the specific email it was granted for, never to this
    // component instance. The detail surface reuses the same renderer across
    // records, so a mount-scoped flag would silently carry one sender's opt-in to
    // the next email. Identity keys on the record id AND the body, so neither a
    // new record with a coincidentally identical body, nor a changed body, reuses
    // an earlier opt-in — every email is asked for on its own.
    const [allowedIdentity, setAllowedIdentity] = React.useState<string | null>(null);
    const raw = value === null || value === undefined ? '' : String(value);
    const recordId = record ? host.getRecordValue(record, 'Id') : undefined;
    const identity = `${typeof recordId === 'string' ? recordId : ''} ${raw}`;
    const remoteAllowed = raw !== '' && allowedIdentity === identity;

    const { safeHtml, fullHtml } = React.useMemo(() => ({
      safeHtml: raw ? host.sanitizeHtml(raw, { blockRemoteContent: true }) : '',
      fullHtml: raw ? host.sanitizeHtml(raw) : '',
    }), [raw]);

    if (!raw) return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
    const text = host.htmlToText(raw);
    if (!text) return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;

    if (variant !== 'detail') {
      return <span className="block min-w-0 truncate text-slate-700" title={text}>{text}</span>;
    }

    // Only advertise the opt-in when something is actually being withheld, so a
    // plain-text email does not grow a banner that does nothing.
    const hasRemoteContent = safeHtml !== fullHtml;
    return (
      <div>
        {hasRemoteContent && !remoteAllowed && (
          <div className="mb-2 flex flex-wrap items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
            <span>Remote content (images, tracking pixels) is blocked.</span>
            <button
              type="button"
              className="font-medium text-amber-900 underline hover:no-underline"
              onClick={(event) => { event.stopPropagation(); setAllowedIdentity(identity); }}
            >
              Load remote content
            </button>
          </div>
        )}
        <div
          className="prose prose-sm max-w-none text-slate-700 break-words"
          onClick={(event) => {
            if ((event.target as HTMLElement).closest('a')) event.stopPropagation();
          }}
          dangerouslySetInnerHTML={{ __html: remoteAllowed ? fullHtml : safeHtml }}
        />
      </div>
    );
  };

  const fieldRenderers = {
    boolean: BooleanRenderer,
    picklist: PicklistRenderer,
    multipicklist: MultipicklistRenderer,
    url: UrlRenderer,
    email: EmailRenderer,
    phone: PhoneRenderer,
    reference: LookupRenderer,
    richtextarea: RichTextRenderer,
    textarea: TextareaRenderer,
    currency: ({ value, record, field }) => {
      const currencyCode = getRecordCurrencyCode(host, record);
      if (record && field) {
        const displayVal = host.getRecordValue(record, `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`);
        if (displayVal && !currencyCode) return <span>{String(displayVal)}</span>;
      }
      if (value === null || value === undefined || value === '') return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
      const numVal = Number(value);
      if (Number.isNaN(numVal)) return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
      return <span>{host.formatCurrency(numVal, 2, currencyCode)}</span>;
    },
    date: ({ value, record, field }) => renderDisplayValue(value, record, field, host.formatDate),
    datetime: ({ value, record, field }) => renderDisplayValue(value, record, field, host.formatDateTime),
    double: ({ value, record, field }) => {
      if (record && field) {
        const displayVal = host.getRecordValue(record, `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`);
        if (displayVal) return <span>{String(displayVal)}</span>;
      }
      if (value === null || value === undefined || value === '') return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
      const numVal = Number(value);
      if (Number.isNaN(numVal)) return <span>{String(value)}</span>;
      const rounded = Math.round(numVal * 100) / 100;
      return <span>{rounded.toLocaleString()}</span>;
    },
    percent: ({ value, record, field }) => {
      if (record && field) {
        const displayVal = host.getRecordValue(record, `${field.name}${CRM_FIELD_DISPLAY_VALUE_SUFFIX}`);
        if (displayVal) return <span>{String(displayVal)}</span>;
      }
      if (value === null || value === undefined || value === '') return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
      const numVal = Number(value);
      if (Number.isNaN(numVal)) return <span className="text-slate-400">{CRM_EMPTY_FIELD_DISPLAY}</span>;
      const rounded = Math.round(numVal * 100) / 100;
      return <span>{rounded}%</span>;
    },
  } satisfies Record<CrmUiFieldRendererType, React.FC<CrmUiFieldRendererProps>>;

  const fieldInputs = {
    picklist: PicklistInput,
    multipicklist: MultipicklistInput,
    reference: ReferenceInput,
    textarea: TextareaInput,
    boolean: BooleanInput,
  } satisfies Record<CrmUiFieldInputType, React.FC<CrmUiFieldInputProps>>;

  const fieldRendererOverrides: readonly CrmUiFieldRendererOverride[] = [
    {
      objectApiName: CRM_EMAIL_MESSAGE_OBJECT,
      fieldApiName: CRM_EMAIL_MESSAGE_HTML_BODY_FIELD,
      renderer: EmailHtmlRenderer,
    },
  ];

  return { fieldRenderers, fieldInputs, fieldRendererOverrides };
}

export {
  CRM_UI_FIELD_INPUT_TYPES,
  CRM_UI_FIELD_RENDERER_TYPES,
};
