/**
 * Shared formatting helpers for activity-feed surfaces. Centralises the
 * humanise/diff logic so the dashboard activity widget and per-entity
 * activity feeds render audit-log rows with the same vocabulary and
 * shape. Each surface still owns its own JSX; only the strings flow
 * through this module.
 */

import { STAGE_LABELS, formatSource, type PipelineStage, PIPELINE_STAGES } from '@/lib/constants';

/** Map legacy 9-stage enum keys to their modern 7-stage counterparts so
 *  historical rows still render with current labels. */
const LEGACY_STAGE_REMAP: Record<string, PipelineStage> = {
  deposit_10pct: 'deposit_paid',
  contract_sent: 'contract',
  contract_signed: 'contract',
};

const ACTION_VERBS: Record<string, string> = {
  create: 'created',
  update: 'updated',
  delete: 'deleted',
  archive: 'archived',
  restore: 'restored',
  merge: 'merged',
  revert: 'reverted',
  transfer: 'transferred',
  cancel: 'cancelled',
  send: 'sent',
  sign: 'signed',
  complete: 'completed',
  reject: 'rejected',
  enable: 'enabled',
  disable: 'disabled',
  pause: 'paused',
  resume: 'resumed',
  void: 'voided',
  permission_denied: 'denied permission for',
};

/** Past-tense verb for an audit `action`. Falls back to the raw enum so
 *  unknown actions still surface (rather than silently dropping). */
export function actionVerb(action: string): string {
  return ACTION_VERBS[action] ?? action;
}

/** Visual variant for the action badge - keeps colours consistent across
 *  every feed surface. */
export function actionVariant(action: string): 'default' | 'secondary' | 'destructive' | 'outline' {
  switch (action) {
    case 'create':
    case 'send':
    case 'sign':
    case 'complete':
    case 'enable':
    case 'resume':
      return 'default';
    case 'update':
    case 'restore':
    case 'transfer':
    case 'merge':
      return 'secondary';
    case 'delete':
    case 'reject':
    case 'cancel':
    case 'void':
      return 'destructive';
    case 'archive':
    case 'disable':
    case 'pause':
    case 'revert':
    default:
      return 'outline';
  }
}

/** camelCase / snake_case field name -> "Title Case" so the audit log
 *  reads naturally ("fullName" -> "Full Name", "phone_number" -> "Phone
 *  Number"). Single-word fields stay capitalised. */
export function humanizeFieldName(name: string): string {
  return name
    .replace(/_/g, ' ')
    .replace(/([a-z])([A-Z])/g, '$1 $2')
    .replace(/\b\w/g, (c) => c.toUpperCase());
}

/** Entity type alias map for the feed labels. Most types humanise fine
 *  via `humanizeFieldName`, but a few read awkwardly (notes flatten to
 *  their parent, multi-word types stay multi-word). */
const ENTITY_TYPE_LABELS: Record<string, string> = {
  residential_client: 'Residential client',
  residential_interest: 'Residential interest',
  berth_tenancy: 'Berth tenancy',
  berth_maintenance_log: 'Berth maintenance',
  berth_recommendation: 'Berth recommendation',
  client_note: 'Client note',
  yacht_note: 'Yacht note',
  company_note: 'Company note',
  interest_note: 'Interest note',
  interest_qualification: 'Interest qualification',
  document_send: 'Document send',
  document_folder: 'Document folder',
  document_template: 'Document template',
  documentTemplate: 'Document template',
  form_template: 'Form template',
  report_template: 'Report template',
  email_account: 'Email account',
  email_message: 'Email message',
  user_email_change: 'Email change',
  custom_field_definition: 'Custom field',
  custom_field_values: 'Custom field',
  expense_export: 'Expense export',
  gdpr_export: 'GDPR export',
  qualification_criterion: 'Qualification criterion',
  website_submission: 'Website submission',
  webhook_inbound: 'Inbound webhook',
  webhook_delivery: 'Webhook delivery',
  audit_log: 'Audit log',
  portal_user: 'Portal user',
  portal_session: 'Portal session',
  portal_auth_token: 'Portal token',
  client_contact: 'Client contact',
  clientContact: 'Client contact',
  clientAddress: 'Client address',
  companyAddress: 'Company address',
  clientRelationship: 'Client relationship',
  company_membership: 'Company membership',
  crm_invite: 'CRM invite',
  queue_job: 'Queue job',
  super_admin: 'Super admin',
};

export function humanizeEntityType(type: string): string {
  return ENTITY_TYPE_LABELS[type] ?? humanizeFieldName(type);
}

/** Map enum-typed field values to their canonical human labels. The audit
 *  log stores raw enum strings; the feed should render them as the
 *  human form. */
export function normalizeEnumValue(field: string, value: unknown): unknown {
  if (typeof value !== 'string') return value;
  const f = field.replace(/_/g, '').toLowerCase();
  if (f === 'pipelinestage' || f === 'stage') {
    const modern = (PIPELINE_STAGES as readonly string[]).includes(value)
      ? (value as PipelineStage)
      : LEGACY_STAGE_REMAP[value];
    if (modern) return STAGE_LABELS[modern];
    return humanizeFieldName(value);
  }
  if (f === 'source') {
    return formatSource(value) ?? value;
  }
  if (f === 'leadcategory' || f === 'category' || f === 'outcome') {
    return humanizeFieldName(value);
  }
  return value;
}

/** Render a JSON-ish value as a short, single-line preview. Strings come
 *  through as-is; objects flatten to "k: v, k: v"; arrays compress to a
 *  count; nulls / empty render as em-dash. */
export function shortValue(value: unknown, fieldContext?: string): string {
  if (fieldContext) value = normalizeEnumValue(fieldContext, value);
  if (value === null || value === undefined || value === '') return '-';
  if (typeof value === 'string') return value;
  if (typeof value === 'number' || typeof value === 'boolean') return String(value);
  if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? '' : 's'}`;
  if (typeof value === 'object') {
    const entries = Object.entries(value as Record<string, unknown>);
    if (entries.length === 0) return '-';
    return entries
      .slice(0, 3)
      .map(
        ([k, v]) =>
          `${humanizeFieldName(k)}: ${
            typeof v === 'string' ? normalizeEnumValue(k, v) : JSON.stringify(v)
          }`,
      )
      .join(', ');
  }
  return String(value);
}

export interface DiffSource {
  action: string;
  fieldChanged: string | null;
  oldValue: unknown;
  newValue: unknown;
}

/** Build a "Field: old -> new" diff string. Returns null when there's
 *  nothing useful to render. Audit logs store the per-field diff inside
 *  `oldValue` as `{ field: { old, new }, ... }` (see entity-diff.ts), so
 *  that's the shape we pattern-match first. Falls back to a
 *  fieldChanged/old->new pair, then to a key-by-key compare of two flat
 *  objects in `oldValue` vs `newValue`. */
export function buildDiffLine(item: DiffSource): string | null {
  if (
    item.action === 'update' &&
    item.oldValue &&
    typeof item.oldValue === 'object' &&
    !Array.isArray(item.oldValue)
  ) {
    const diffMap = item.oldValue as Record<string, unknown>;
    const entries = Object.entries(diffMap).filter(([, v]) => {
      return v && typeof v === 'object' && 'old' in (v as object) && 'new' in (v as object);
    });
    if (entries.length > 0) {
      return entries
        .slice(0, 2)
        .map(([field, v]) => {
          const { old, new: nextValue } = v as { old: unknown; new: unknown };
          return `${humanizeFieldName(field)}: ${shortValue(old, field)} → ${shortValue(
            nextValue,
            field,
          )}`;
        })
        .join(' · ');
    }
  }

  if (item.fieldChanged) {
    const field = item.fieldChanged;
    return `${humanizeFieldName(field)}: ${shortValue(item.oldValue, field)} → ${shortValue(
      item.newValue,
      field,
    )}`;
  }

  if (
    item.action === 'update' &&
    item.oldValue &&
    typeof item.oldValue === 'object' &&
    item.newValue &&
    typeof item.newValue === 'object'
  ) {
    const oldObj = item.oldValue as Record<string, unknown>;
    const newObj = item.newValue as Record<string, unknown>;
    const keys = Object.keys(oldObj).filter((k) => k in newObj);
    if (keys.length === 0) return null;
    return keys
      .slice(0, 2)
      .map(
        (k) => `${humanizeFieldName(k)}: ${shortValue(oldObj[k], k)} → ${shortValue(newObj[k], k)}`,
      )
      .join(' · ');
  }

  return null;
}
