     1	import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
     2	
     3	/**
     4	 * Processes JSON:API relationship data and converts to database operations
     5	 *
     6	 * @param {Object} scope - The scope object containing schema info
     7	 * @param {Object} deps - Dependencies object
     8	 * @returns {Object} Object with belongsToUpdates and manyToManyRelationships arrays
     9	 *
    10	 * @example
    11	 * // Input: Simple belongsTo relationship
    12	 * const inputRecord = {
    13	 *   data: {
    14	 *     type: 'articles',
    15	 *     attributes: { title: 'My Article' },
    16	 *     relationships: {
    17	 *       author: {
    18	 *         data: { type: 'users', id: '123' }
    19	 *       },
    20	 *       category: {
    21	 *         data: { type: 'categories', id: '5' }
    22	 *       }
    23	 *     }
    24	 *   }
    25	 * };
    26	 *
    27	 * // Schema has:
    28	 * // author_id: { belongsTo: 'users', as: 'author' }
    29	 * // category_id: { belongsTo: 'categories', as: 'category' }
    30	 *
    31	 * const result = processRelationships(scope, { context: { inputRecord } });
    32	 *
    33	 * // Output: Foreign keys extracted
    34	 * // {
    35	 * //   belongsToUpdates: {
    36	 * //     author_id: '123',
    37	 * //     category_id: '5'
    38	 * //   },
    39	 * //   manyToManyRelationships: []
    40	 * // }
    41	 *
    42	 * @example
    43	 * // Input: Polymorphic relationship
    44	 * const inputRecord = {
    45	 *   data: {
    46	 *     type: 'comments',
    47	 *     relationships: {
    48	 *       commentable: {
    49	 *         data: { type: 'posts', id: '456' }
    50	 *       }
    51	 *     }
    52	 *   }
    53	 * };
    54	 *
    55	 * // Schema relationships has:
    56	 * // commentable: {
    57	 * //   belongsToPolymorphic: {
    58	 * //     types: ['posts', 'videos'],
    59	 * //     typeField: 'commentable_type',
    60	 * //     idField: 'commentable_id'
    61	 * //   }
    62	 * // }
    63	 *
    64	 * const result = processRelationships(scope, { context: { inputRecord } });
    65	 *
    66	 * // Output: Both type and id fields set
    67	 * // {
    68	 * //   belongsToUpdates: {
    69	 * //     commentable_type: 'posts',
    70	 * //     commentable_id: '456'
    71	 * //   },
    72	 * //   manyToManyRelationships: []
    73	 * // }
    74	 *
    75	 * @example
    76	 * // Input: Many-to-many relationship
    77	 * const inputRecord = {
    78	 *   data: {
    79	 *     type: 'articles',
    80	 *     relationships: {
    81	 *       tags: {
    82	 *         data: [
    83	 *           { type: 'tags', id: '10' },
    84	 *           { type: 'tags', id: '20' }
    85	 *         ]
    86	 *       }
    87	 *     }
    88	 *   }
    89	 * };
    90	 *
    91	 * // Schema relationships has:
    92	 * // tags: {
    93	 * //   type: 'manyToMany',
    94	 * //   through: 'article_tags',
    95	 * //   foreignKey: 'article_id',
    96	 * //   otherKey: 'tag_id'
    97	 * // }
    98	 *
    99	 * const result = processRelationships(scope, { context: { inputRecord } });
   100	 *
   101	 * // Output: Many-to-many data collected
   102	 * // {
   103	 * //   belongsToUpdates: {},
   104	 * //   manyToManyRelationships: [{
   105	 * //     relName: 'tags',
   106	 * //     relDef: {
   107	 * //       through: 'article_tags',
   108	 * //       foreignKey: 'article_id',
   109	 * //       otherKey: 'tag_id'
   110	 * //     },
   111	 * //     relData: [
   112	 * //       { type: 'tags', id: '10' },
   113	 * //       { type: 'tags', id: '20' }
   114	 * //     ]
   115	 * //   }]
   116	 * // }
   117	 *
   118	 * @example
   119	 * // Input: Clearing relationships
   120	 * const inputRecord = {
   121	 *   data: {
   122	 *     relationships: {
   123	 *       author: { data: null },      // Clear belongsTo
   124	 *       tags: { data: [] }           // Clear many-to-many
   125	 *     }
   126	 *   }
   127	 * };
   128	 *
   129	 * const result = processRelationships(scope, { context: { inputRecord } });
   130	 *
   131	 * // Output: Null for belongsTo, empty array for many-to-many
   132	 * // {
   133	 * //   belongsToUpdates: { author_id: null },
   134	 * //   manyToManyRelationships: [{
   135	 * //     relName: 'tags',
   136	 * //     relDef: { ... },
   137	 * //     relData: []  // Will delete all pivot records
   138	 * //   }]
   139	 * // }
   140	 *
   141	 * @description
   142	 * Used by:
   143	 * - rest-api-knex-plugin's dataPut and dataPatch methods
   144	 * - Called before database writes to prepare relationship updates
   145	 *
   146	 * Purpose:
   147	 * - Converts JSON:API relationship format to database operations
   148	 * - Handles all relationship types: belongsTo, polymorphic, many-to-many
   149	 * - Validates polymorphic types against allowed values
   150	 * - Separates concerns: foreign keys vs pivot table operations
   151	 *
   152	 * Data flow:
   153	 * 1. Receives JSON:API document with relationships section
   154	 * 2. Analyzes schema to determine relationship types
   155	 * 3. For belongsTo: extracts foreign key values
   156	 * 4. For polymorphic: extracts both type and id fields
   157	 * 5. For many-to-many: collects data for pivot operations
   158	 * 6. Returns structured data for database updates
   159	 */
   160	export const processRelationships = (scope, deps) => {
   161	  // Extract values from scope
   162	  const {
   163	    vars: {
   164	      schemaInfo: { schemaStructure: schemaFields, schemaRelationships: relationships }
   165	    }
   166	  } = scope
   167	
   168	  // Extract values from deps
   169	  const { context } = deps
   170	  const { inputRecord } = context
   171	  const belongsToUpdates = {}
   172	  const manyToManyRelationships = []
   173	
   174	  if (!inputRecord.data.relationships) {
   175	    return { belongsToUpdates, manyToManyRelationships }
   176	  }
   177	
   178	  for (const [relName, relData] of Object.entries(inputRecord.data.relationships)) {
   179	    const relDef = relationships?.[relName]
   180	
   181	    // Find the schema field that defines this relationship
   182	    const schemaField = Object.entries(schemaFields).find(([fieldName, fieldDef]) =>
   183	      fieldDef.as === relName
   184	    )
   185	
   186	    if (schemaField) {
   187	      const [fieldName, fieldDef] = schemaField
   188	
   189	      // Handle regular belongsTo (1:1)
   190	      if (fieldDef.belongsTo) {
   191	        if (relData.data === null) {
   192	          belongsToUpdates[fieldName] = null
   193	        } else if (relData.data?.id) {
   194	          belongsToUpdates[fieldName] = relData.data.id
   195	        }
   196	      }
   197	    }
   198	
   199	    // Check for polymorphic belongsTo defined in relationships object (not as schema field)
   200	    if (!schemaField && relDef?.belongsToPolymorphic && relData.data !== undefined) {
   201	      if (relData.data === null) {
   202	        const { typeField, idField } = relDef.belongsToPolymorphic
   203	        belongsToUpdates[typeField] = null
   204	        belongsToUpdates[idField] = null
   205	      } else if (relData.data) {
   206	        const { type, id } = relData.data
   207	        const { types, typeField, idField } = relDef.belongsToPolymorphic
   208	
   209	        // Validate type is allowed
   210	        if (!types.includes(type)) {
   211	          throw new RestApiValidationError(
   212	            `Invalid type '${type}' for polymorphic relationship '${relName}'. Allowed types: ${types.join(', ')}`,
   213	            {
   214	              fields: [`data.relationships.${relName}.data.type`],
   215	              violations: [{
   216	                field: `data.relationships.${relName}.data.type`,
   217	                rule: 'polymorphic_type',
   218	                message: `Type must be one of: ${types.join(', ')}`
   219	              }]
   220	            }
   221	          )
   222	        }
   223	
   224	        belongsToUpdates[typeField] = type
   225	        belongsToUpdates[idField] = id
   226	      }
   227	    }
   228	
   229	    // Check relationship type and process accordingly
   230	    if (relDef?.type === 'manyToMany' && relData.data !== undefined) {
   231	      manyToManyRelationships.push({
   232	        relName,
   233	        relDef: {
   234	          through: relDef.through,
   235	          foreignKey: relDef.foreignKey,
   236	          otherKey: relDef.otherKey
   237	        },
   238	        relData: relData.data || []  // null means empty array for many-to-many
   239	      })
   240	    }
   241	    // Note: hasOne and hasMany don't need processing here as they don't update the current record
   242	  }
   243	
   244	  return { belongsToUpdates, manyToManyRelationships }
   245	}
