import byType from "./types/index.js";
export const getTypedDefinitions = (schema) => schema.definitions;
export function set(dataCtx, value, instancePath) {
    if (dataCtx && dataCtx.parentData && dataCtx.parentDataProperty !== "") {
        // @ts-ignore: later
        dataCtx.parentData[dataCtx.parentDataProperty] = value;
    }
    else {
        throw new Error('In "' +
            instancePath +
            '", cannot set value ' +
            JSON.stringify(value) +
            " to context " +
            JSON.stringify(dataCtx));
    }
}
/*
function yahooFinanceType(
  data: unknown,
  schema: string,
  _definitions: JSONSchema["definitions"] | undefined,
  errors: ValidationError[],
  instancePath: string,
  dataCtx?: DataCtx,
  schemaPath: string = "",
) {
  if (schema === "DateInMs") {
    if (typeof data === "number") {
      return set(dataCtx, new Date(data), instancePath);
    } else {
      errors.push({
        // keyword: "yahooFinanceType",
        instancePath,
        schemaPath,
        message: "yahooFinanceDate/dateInMs: Expected a number",
        // params: { schema, data },
        schema,
        data,
      });
      return false;
    }
  } else {
    errors.push({
      instancePath,
      schemaPath,
      // keyword: "yahooFinanceType",
      message: "yahooFinanceType: no matching type",
      // params: { schema, data },
      data,
      schema,
    });
    return false;
  }

  // return true;
}
*/
function schemaFromSchemaOrSchemaKey(schemaOrSchemaKey, definitions) {
    let schema;
    let path = "";
    if (!definitions) {
        throw new Error("No definitions provided");
    }
    if (typeof schemaOrSchemaKey === "string") {
        const definition = schemaOrSchemaKey.match(/^#\/definitions\/(.*)$/)?.[1];
        if (!definition) {
            throw new Error("No such schema with key: " + schemaOrSchemaKey);
        }
        schema = definitions[definition];
        path = schemaOrSchemaKey;
        if (!schema) {
            throw new Error(`No such schema with key: ${definition}`);
        }
    }
    else {
        schema = schemaOrSchemaKey;
        if (schema.$id)
            path = schema.$id;
    }
    let refs;
    while (schema.$ref) {
        if (!refs)
            refs = [];
        const ref = schema.$ref.replace("#/definitions/", "");
        refs.push(ref);
        schema = definitions[ref];
        path = schema.$ref;
    }
    return [schema, path, refs];
}
export default function validateAndCoerce(input, schemaOrSchemaKey, ctx, errors = [], instancePath = "", dataCtx, schemaPath = null) {
    const [schema, foundSchemaPath, refs] = schemaFromSchemaOrSchemaKey(schemaOrSchemaKey, ctx.definitions);
    if (foundSchemaPath)
        schemaPath = foundSchemaPath;
    if (schema.anyOf) {
        const allErrors = [];
        let _errors = [];
        /// Since yahooFinanceType can mutate, we need to save unmodified state.
        const serializedInput = JSON.stringify(input);
        let i = 0;
        for (const subSchema of schema.anyOf) {
            const subSchemaPath = subSchema.$ref ||
                schemaPath + "/anyOf/" + (i++).toString();
            _errors = [];
            validateAndCoerce(input, subSchema, ctx, _errors, instancePath, dataCtx, subSchemaPath);
            if (!_errors.length)
                break;
            // allErrors.push(subSchema);
            allErrors.push(..._errors);
            if (dataCtx?.parentData) {
                input = serializedInput === undefined
                    ? undefined
                    : JSON.parse(serializedInput);
                // @ts-ignore: it's ok
                dataCtx.parentData[dataCtx.parentDataProperty] = input;
            }
        }
        if (_errors.length) {
            errors.push({
                instancePath,
                schemaPath: schemaPath, // ! because of "if null" check above
                message: "should match some schema in anyOf",
                data: input,
                // schema,
                subErrors: allErrors,
            });
            // return false;
            return errors;
        }
        /*
      } else if (schema.yahooFinanceType) {
        yahooFinanceType(
          input,
          schema.yahooFinanceType,
          definitions,
          errors,
          instancePath,
          dataCtx,
          schemaPath!, // ! because of "if null" check above
        );
      */
    }
    else if (schema.oneOf) {
        if (!("discriminator" in schema)) {
            throw new Error("oneOf without discriminator not supported yet");
        }
        const discriminator = schema.discriminator
            ?.propertyName;
        const allErrors = [];
        let _errors = [];
        /// Since yahooFinanceType can mutate, we need to save unmodified state.
        const serializedInput = JSON.stringify(input);
        let i = 0;
        for (const subSchema of schema.oneOf) {
            const subSchemaPath = subSchema.$ref ||
                schemaPath + "/oneOf/" + (i++).toString();
            _errors = [];
            const _subSchema = schemaFromSchemaOrSchemaKey(subSchema, ctx.definitions)[0];
            if (_subSchema.properties?.hasOwnProperty(discriminator)) {
                validateAndCoerce(input, subSchema, ctx, _errors, instancePath, dataCtx, subSchemaPath);
            }
            else {
                _errors.push({
                    instancePath,
                    schemaPath: subSchemaPath,
                    message: `Missing discriminator field "${discriminator}" in schema`,
                    data: input,
                });
            }
            if (!_errors.length)
                break;
            if (_errors.some((error) => error.instancePath === "/" + discriminator)) {
                // For now, let's exclude these errors
                // allErrors.push(..._errors);
                // It would be better to handle this at a later stage, but for now, the main
                // reason we're doing this is to make the errors easier to read so all we
                // really care about is not pushing superfluous errors. XXX
            }
            else {
                allErrors.push(..._errors);
            }
            if (dataCtx?.parentData) {
                input = serializedInput === undefined
                    ? undefined
                    : JSON.parse(serializedInput);
                // @ts-ignore: it's ok
                dataCtx.parentData[dataCtx.parentDataProperty] = input;
            }
        }
        if (_errors.length) {
            errors.push({
                instancePath,
                schemaPath: schemaPath, // ! because of "if null" check above
                message: "should match some schema in oneOf",
                params: { discriminator },
                data: input,
                // schema,
                subErrors: allErrors,
            });
            // return false;
            return errors;
        }
    }
    else {
        if (schema.type === undefined) {
            // This is actually a no-op.  With schema of {}, accept anything and everything.
            // console.log(`No type in ${instancePath}`);
            // throw new Error(`No type in ${instancePath}`);
            /*
            // TODO, need accesss to parent schema for this
            if (parentSchema.patternProperties)
              throw new Error(
                `patternProperties needed but not supported yet, ${instancePath}`,
              );
            */
        }
        else if (Array.isArray(schema.type)) {
            let _errors = [];
            for (const type of schema.type) {
                _errors = [];
                // @ts-ignore: another day
                const validator = byType[type];
                if (!validator) {
                    throw new Error(`No validator for type ${JSON.stringify(type)} in ${instancePath}`);
                }
                validator(input, schema, ctx, _errors, instancePath, dataCtx, schemaPath, refs);
                if (!_errors.length)
                    break;
            }
            if (_errors.length) {
                errors.push({
                    instancePath,
                    message: `Expected one of ${schema.type.join(", ")}`,
                    data: input,
                });
                // return false;
                return errors;
            }
        }
        else {
            // @ts-ignore: another day
            const validator = byType[schema.type];
            if (!validator) {
                throw new Error(`No validator for type ${JSON.stringify(schema.type)} in ${instancePath}`);
            }
            validator(input, schema, ctx, errors, instancePath, dataCtx, schemaPath, refs);
        }
    }
    return errors;
}
