import forEach from 'lodash/forEach';
import isEqual from 'lodash/isEqual';

import { FormContextType, RJSFSchema, StrictRJSFSchema } from '../types';
import { PROPERTIES_KEY, ITEMS_KEY } from '../constants';
import ParserValidator, { SchemaMap } from './ParserValidator';
import { retrieveSchemaInternal, resolveAnyOrOneOfSchemas } from '../schema/retrieveSchema';

/** Recursive function used to parse the given `schema` belonging to the `rootSchema`. The `validator` is used to
 * capture the sub-schemas that the `isValid()` function is called with. For each schema returned by the
 * `retrieveSchemaInternal()`, the `resolveAnyOrOneOfSchemas()` function is called. For each of the schemas returned
 * from THAT call have `properties`, then each of the sub-schema property objects are then recursively parsed.
 *
 * @param validator - The `ParserValidator` implementation used to capture `isValid()` calls during parsing
 * @param recurseList - The list of schemas returned from the `retrieveSchemaInternal`, preventing infinite recursion
 * @param rootSchema - The root schema from which the schema parsing began
 * @param schema - The current schema element being parsed
 */
function parseSchema<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
  validator: ParserValidator<T, S, F>,
  recurseList: S[],
  rootSchema: S,
  schema: S
) {
  const schemas = retrieveSchemaInternal<T, S, F>(validator, schema, rootSchema, undefined, true);
  schemas.forEach((schema) => {
    const sameSchemaIndex = recurseList.findIndex((item) => isEqual(item, schema));
    if (sameSchemaIndex === -1) {
      recurseList.push(schema);
      const allOptions = resolveAnyOrOneOfSchemas<T, S, F>(validator, schema, rootSchema, true);
      allOptions.forEach((s) => {
        if (PROPERTIES_KEY in s && s[PROPERTIES_KEY]) {
          forEach(schema[PROPERTIES_KEY], (value) => {
            parseSchema<T, S, F>(validator, recurseList, rootSchema, value as S);
          });
        }
      });
      if (ITEMS_KEY in schema && !Array.isArray(schema.items) && typeof schema.items !== 'boolean') {
        parseSchema<T, S, F>(validator, recurseList, rootSchema, schema.items as S);
      }
    }
  });
}

/** Parses the given `rootSchema` to extract out all the sub-schemas that maybe contained within it. Returns a map of
 * the hash of the schema to schema/sub-schema.
 *
 * @param rootSchema - The root schema to parse for sub-schemas used by `isValid()` calls
 * @returns - The `SchemaMap` of all schemas that were parsed
 */
export default function schemaParser<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
  rootSchema: S
): SchemaMap<S> {
  const validator = new ParserValidator<T, S, F>(rootSchema);
  const recurseList: S[] = [];

  parseSchema(validator, recurseList, rootSchema, rootSchema);

  return validator.getSchemaMap();
}
