/**
 * Partner-provisioned merchant hierarchy.
 * Per docs/backend/PARTNER-PROVISIONING-API-CONTRACT.md §2, §10.
 *
 * Deliberately generic: no partner name appears in any type, field or status
 * value here. Everything partner-varying (level naming, which levels a given
 * partner uses) lives in PartnerProfile, not in this model.
 */

/** Four fixed levels. Middle levels are optional per partner, depth is not partner-defined. */
export type NodeLevel = 'company' | 'project' | 'store' | 'payment_point';

export type Environment = 'TEST' | 'LIVE';

/** `disabled` is terminal - it never returns to any other status (§2). */
export type NodeStatus = 'active' | 'suspended' | 'disabled';

export interface ProvisioningNode {
  id: string;
  level: NodeLevel;
  /** null only for level 'company'. */
  parentId: string | null;
  /** Denormalized root, present on every node. */
  companyId: string;
  /** Ordered ancestor ids, root first, inclusive of self. Server-computed, never client-sent. */
  path: string[];
  environment: Environment;
  status: NodeStatus;
  /** The partner's own id for this node. Unique per (companyId, environment, level). */
  externalReference: string;
  displayName: string;
  createdAt: string;
  updatedAt: string;
}

/**
 * A payment_point is an acceptance channel - one payment method at one store.
 * Not a physical till, not a settlement account (§10.1).
 */
export interface PaymentPointConfig {
  method: PaymentMethod;
  /** ISO 4217 subset this channel accepts. */
  currencies: string[];
  /**
   * Opaque provider-side binding, set only by financial enablement.
   * Its absence is what makes a registered payment point not-yet-live:
   * creating a node never enables real money (§2 invariant 6).
   */
  providerAccountRef?: string;
}

/** Both ship today (src/app/pages/cart/cart.component.ts). Extensible. */
export type PaymentMethod = 'qr' | 'card';

export interface CreateNodeRequest {
  externalReference: string;
  displayName: string;
  /** Opaque to us, echoed back, never interpreted. */
  metadata?: Record<string, string>;
}

/** The child level that may be created under a given level, or null at the leaf. */
export function childLevelOf(level: NodeLevel): NodeLevel | null {
  switch (level) {
    case 'company': return 'project';
    case 'project': return 'store';
    case 'store': return 'payment_point';
    case 'payment_point': return null;
  }
}

/** True when a node can currently accept payments (§2 status table). */
export function acceptsPayments(node: ProvisioningNode): boolean {
  return node.status === 'active';
}

/**
 * `disabled` is terminal and cascades; `suspended` is reversible.
 * Encoded here so UI and any future client-side guard agree on one rule.
 */
export function canTransitionTo(from: NodeStatus, to: NodeStatus): boolean {
  if (from === 'disabled') {
    return false;
  }
  if (from === to) {
    return false;
  }
  return to === 'active' || to === 'suspended' || to === 'disabled';
}

/** Builds the ordered root-to-leaf chain for a node from a flat set. */
export function ancestorsOf(node: ProvisioningNode, all: readonly ProvisioningNode[]): ProvisioningNode[] {
  const byId = new Map(all.map(n => [n.id, n]));
  return node.path
    .map(id => byId.get(id))
    .filter((n): n is ProvisioningNode => n !== undefined);
}

/** Direct children of a node, in stable displayName order. */
export function childrenOf(nodeId: string, all: readonly ProvisioningNode[]): ProvisioningNode[] {
  return all
    .filter(n => n.parentId === nodeId)
    .sort((a, b) => a.displayName.localeCompare(b.displayName));
}
