import type { GraphNode, LabelMode, LabelConfig, GraphEdge } from './types.js';
import { PrefixManager } from './prefix-manager.js';

/** Default label predicates in priority order */
const DEFAULT_LABEL_PREDICATES = [
  'http://www.w3.org/2000/01/rdf-schema#label',
  'https://schema.org/name',
  'http://xmlns.com/foaf/0.1/name',
  'http://purl.org/dc/terms/title',
  'http://purl.org/dc/elements/1.1/title',
  'https://schema.org/alternateName',
];

/**
 * Resolves display labels for nodes and edges based on the current label mode.
 *
 * - "strict": Shows compact prefixed URI (e.g., "schema:Person", "prov:wasGeneratedBy")
 * - "humanized": Shows friendly label (e.g., "Person", "was generated by")
 */
export class LabelResolver {
  private _mode: LabelMode;
  private _prefixManager: PrefixManager;
  private _labelPredicates: string[];

  constructor(mode: LabelMode, prefixManager: PrefixManager, config?: LabelConfig) {
    this._mode = mode;
    this._prefixManager = prefixManager;
    this._labelPredicates = config?.predicates ?? DEFAULT_LABEL_PREDICATES;
  }

  get mode(): LabelMode {
    return this._mode;
  }

  set mode(value: LabelMode) {
    this._mode = value;
  }

  /** Resolve a display label for a node */
  resolveNodeLabel(node: GraphNode): string {
    // First: check if the node has an explicit label property
    for (const pred of this._labelPredicates) {
      const values = node.properties.get(pred);
      if (values && values.length > 0) {
        return values[0].value;
      }
    }

    // Fall back to the URI-derived label
    return this.resolveUri(node.id);
  }

  /** Resolve a display label for an edge */
  resolveEdgeLabel(edge: GraphEdge): string {
    return this.resolveUri(edge.predicate);
  }

  /** Resolve a display label for a URI */
  resolveUri(uri: string): string {
    if (this._mode === 'strict') {
      return this._prefixManager.compact(uri) ?? PrefixManager.localName(uri);
    }

    // Humanized mode
    return humanize(PrefixManager.localName(uri));
  }

  /** Resolve a type label (used for type badges) */
  resolveTypeLabel(typeUri: string): string {
    if (this._mode === 'strict') {
      return this._prefixManager.compact(typeUri) ?? PrefixManager.localName(typeUri);
    }
    return humanize(PrefixManager.localName(typeUri));
  }

  /** Resolve label for a predicate (used in property lists) */
  resolvePredicateLabel(predicateUri: string): string {
    return this.resolveUri(predicateUri);
  }

  /**
   * Update all labels on nodes and edges in the graph.
   * Call this after changing label mode or adding new data.
   */
  updateLabels(nodes: Map<string, GraphNode>, edges: Map<string, GraphEdge>): void {
    for (const node of nodes.values()) {
      node.label = this.resolveNodeLabel(node);
    }
    for (const edge of edges.values()) {
      edge.label = this.resolveEdgeLabel(edge);
    }
  }
}

/**
 * Convert a camelCase or PascalCase local name into a human-friendly label.
 * e.g., "wasGeneratedBy" → "was generated by"
 * e.g., "SocialMediaPosting" → "Social Media Posting"
 */
function humanize(localName: string): string {
  if (!localName) return '';

  // Handle snake_case
  let result = localName.replace(/_/g, ' ');

  // Insert spaces before capitals in camelCase/PascalCase
  result = result.replace(/([a-z])([A-Z])/g, '$1 $2');
  result = result.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');

  // Lowercase everything for humanized display (except if it's a short acronym)
  if (result.length > 4) {
    result = result.toLowerCase();
  }

  return result.trim();
}
