const changeCase = require('change-case');

// flatMap: https://gist.github.com/samgiles/762ee337dff48623e729
// [B](f: (A) ⇒ [B]): [B]  ; Although the types in the arrays aren't strict (:
Array.prototype.flatMap = function(lambda) {
  return Array.prototype.concat.apply([], this.map(lambda));
};
Array.prototype.groupBy = function(lambda) {
  return Object.entries(this.reduce((agg, x) => {
    const k = lambda(x);
    (agg[k] = agg[k] || []).push(x);
    return agg;
  }, {}));
}

function preamble() {
  return `\
///////////////////////////////////////////////
//                                           //
//                     !                     //
//   This file is automatically generated!   //
//           Do not directly edit!           //
//                                           //
///////////////////////////////////////////////`;
}

function capitalize(input) {
  return input[0].toUpperCase() + input.slice(1);
}

function decapitalize(input) {
  return input[0].toLowerCase() + input.slice(1);
}

function normalizeSchemaName(name) {
  return name.replace(/DTO/ig, '');
}

function normalizeArgName(name) {
  const tokens = name.split('_');
  const argName = decapitalize(tokens.map(capitalize).join(''));
  return 'base' === argName ? 'Base' : argName;
}

function normalizePropName(propName, schemaName, value) {
  const out = changeCase.snakeCase(propName);
  if ('type' === out)
    return 'r#' + out;
  return out;
}

function stringifyType(prop, endpoint = null, optional = false, fullpath = true, owned = true) {
  if (prop.anyOf) {
    prop = prop.anyOf[0];
  }

  let enumType = prop['x-enum'];
  if (enumType && 'locale' !== enumType)
    return 'crate::consts::' + changeCase.pascalCase(enumType);

  let refType = prop['$ref'];
  if (refType) {
    return (!endpoint ? '' : changeCase.snakeCase(endpoint) + '::') +
      normalizeSchemaName(refType.slice(refType.indexOf('.') + 1));
  }
  if (optional) {
    return `Option<${stringifyType(prop, endpoint, false, fullpath)}>`;
  }
  switch (prop.type) {
    case 'boolean': return 'bool';
    case 'integer': return ('int32' === prop.format ? 'i32' : 'i64');
    case 'number': return ('float' === prop.format ? 'f32' : 'f64');
    case 'array':
      const subprop = stringifyType(prop.items, endpoint, optional, fullpath, owned);
      return (owned ? (fullpath ? 'std::vec::' : '') + `Vec<${subprop}>` : `&[${subprop}]`);
    case 'string': return (owned ? 'String' : '&str');
    case 'object':
      return 'std::collections::HashMap<' + stringifyType(prop['x-key'], endpoint, optional, fullpath, owned) + ', ' +
        stringifyType(prop.additionalProperties, endpoint, optional, fullpath, owned) + '>';
    default: return prop.type;
  }
}

function formatJsonProperty(name) {
  return `#[serde(rename = "${name}")]`;
}

function formatQueryParamStringify(name, prop, ownedOk = false) {
    switch (prop.type) {
        case 'boolean': return `${name} ? "true" : "false"`;
        case 'string': return name;
        default: return (ownedOk ? '' : '&*') + `${name}.to_string()`;
    }
}

function formatAddQueryParam(param) {
    let k = `"${param.name}"`;
    let name = changeCase.snakeCase(param.name);
    let nc = param.required ? '' : `if let Some(${name}) = ${name} `;
    let prop = param.schema;
    switch (prop.type) {
        case 'array': return `${nc}{ query_params.extend_pairs(${name}.iter()`
            + `.map(|w| (${k}, ${formatQueryParamStringify("w", prop, true)}))); }`;
        case 'object': throw 'unsupported';
        default:
            return `${nc}{ query_params.append_pair(${k}, ${formatQueryParamStringify(name, prop)}); }`;
    }
}

function formatRouteArgument(route, pathParams = []) {
  if (!pathParams.length)
    return `"${route}".to_owned()`;

  route = route.replace(/\{\S+?\}/g, '{}');
  const args = pathParams
    .map(({name}) => name)
    .map(changeCase.snakeCase)
    .join(', ');
  return `format!("${route}", ${args})`;
}

module.exports = {
  changeCase,
  preamble,
  capitalize,
  decapitalize,
  normalizeSchemaName,
  normalizeArgName,
  normalizePropName,
  stringifyType,
  formatJsonProperty,
  formatAddQueryParam,
  formatRouteArgument,
};