/**
 * Shared helper: extract belongsTo relationships from a model.
 *
 * Used by both the form-body composer (emits <select> dropdowns) and
 * view-emitter (generates the import + hook wiring for those
 * dropdowns). Keeping this in one place means form JSX and skeleton
 * substitutions can't drift.
 *
 * Supports both shapes a parsed spec can use:
 *   - Convention string: "belongsTo User cascade"
 *   - Structured object: { type: "belongsTo", target: "User" }
 */

import type { ModelSpec } from './view-emitter.js';

export interface BelongsToRel {
  /** Relationship name — e.g. "owner", "assignee". */
  name: string;
  /** Target model name — e.g. "User". */
  target: string;
}

export function extractBelongsToTargets(model: ModelSpec): BelongsToRel[] {
  const rels = (model.relationships ?? {}) as Record<string, unknown>;
  const out: BelongsToRel[] = [];

  for (const [name, rawDef] of Object.entries(rels)) {
    const parsed = parseBelongsTo(rawDef);
    if (parsed) out.push({ name, target: parsed });
  }
  return out;
}

function parseBelongsTo(def: unknown): string | null {
  // Convention string: "belongsTo User cascade" / "belongsTo User"
  if (typeof def === 'string') {
    const parts = def.trim().split(/\s+/);
    if (parts[0] === 'belongsTo' && parts[1]) return parts[1];
    return null;
  }
  // Structured: { type: "belongsTo", target: "User" } — support all
  // of the field names the parsed spec can use ("target", "to",
  // "model", "targetModel"). The normalizer upstream is inconsistent
  // and normalizing it there is out of scope for this composer.
  if (def && typeof def === 'object') {
    const o = def as { type?: string; target?: string; to?: string; model?: string; targetModel?: string };
    if (o.type === 'belongsTo') {
      return o.target || o.to || o.model || o.targetModel || null;
    }
  }
  return null;
}

/**
 * Minimal English pluralizer, matching view-emitter's and
 * use-api-hooks-starter-generator's convention. Centralizing it here
 * keeps the generated hook names (useUsersQuery) aligned with what
 * the emitter imports.
 */
export function pluralize(s: string): string {
  if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
  if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
  return s + 's';
}

/**
 * Build a map from FK column name → belongsTo relationship. Used by
 * every view composer (list, detail, dashboard, form) to detect when
 * an attribute is really a foreign key and should render as a resolved
 * display name, not a raw UUID.
 *
 * Convention: a belongsTo relationship named `owner` pointing at `User`
 * implies a column `ownerId: UUID` on the owning model. This function
 * materializes that convention.
 */
export function buildFKMap(model: ModelSpec): Map<string, BelongsToRel> {
  const map = new Map<string, BelongsToRel>();
  for (const rel of extractBelongsToTargets(model)) {
    map.set(`${rel.name}Id`, rel);
  }
  return map;
}

/**
 * A hasMany relationship — the "other side" of a belongsTo. E.g.
 * Category has `items: hasMany Item`, which means we can filter
 * Items by `item.categoryId === category.id` to get a Category's
 * items. Used by the detail composer to render tabbed related lists.
 */
export interface HasManyRel {
  /** Relationship name on the parent — e.g. "items", "assignedTasks". */
  name: string;
  /** Target (child) model name — e.g. "Item", "Task". */
  target: string;
  /**
   * FK column on the child that points back to this parent. Typically
   * \`${parentName.toLowerCase()}Id\` — e.g. for \`Item.belongsTo Category\`,
   * Item's FK is \`categoryId\`. Factory B filters by this at render time.
   */
  foreignKey: string;
}

export function extractHasManyTargets(
  model: ModelSpec,
  modelSchemas?: Record<string, ModelSpec>,
): HasManyRel[] {
  const rels = (model.relationships ?? {}) as Record<string, unknown>;
  const out: HasManyRel[] = [];
  const parentLower = model.name.charAt(0).toLowerCase() + model.name.slice(1);

  for (const [name, rawDef] of Object.entries(rels)) {
    const parsed = parseHasMany(rawDef);
    if (!parsed) continue;

    // Determine the FK on the target that points back to this model.
    // Prefer an explicit belongsTo on the target; fall back to the
    // conventional \`${parentLower}Id\`.
    let foreignKey = `${parentLower}Id`;
    if (modelSchemas && modelSchemas[parsed]) {
      const targetRels = (modelSchemas[parsed].relationships ?? {}) as Record<string, unknown>;
      for (const [targetRelName, targetRelDef] of Object.entries(targetRels)) {
        const backTarget = parseBelongsTo(targetRelDef);
        if (backTarget === model.name) {
          foreignKey = `${targetRelName}Id`;
          break;
        }
      }
    }

    out.push({ name, target: parsed, foreignKey });
  }
  return out;
}

function parseHasMany(def: unknown): string | null {
  if (typeof def === 'string') {
    const parts = def.trim().split(/\s+/);
    if (parts[0] === 'hasMany' && parts[1]) return parts[1];
    return null;
  }
  if (def && typeof def === 'object') {
    const o = def as { type?: string; target?: string; to?: string; model?: string; targetModel?: string };
    if (o.type === 'hasMany') {
      return o.target || o.to || o.model || o.targetModel || null;
    }
  }
  return null;
}
