$stylobate_options = {
  'warn_on_incorrect_notation': true
}

$selector_regex = '&|^[\/\.]|ROOT|(^|[^\w])(__|-)\w'

$stylobate_extends = {}
$stylobate_extends_without_defaults = {}

stylobate_extends($what, $target, $defaults = true)
  $stylobate_extends[''+utilus_ident($what)] = $target

  if !$defaults
    $stylobate_extends_without_defaults[''+utilus_ident($what)] = true

$stylobate_defaults = {}

stylobate_defaults($what, $defaults...)
  $stylobate_defaults['' + utilus_ident($what)] = $defaults

stylobate_params($this, $params)
  $collected_params = {
    parent: {},
    arguments: clone($params)
  }
  prepend($collected_params.arguments, unquote(''))
  _stylobate_collect_arguments($collected_params)

  $this['modifiers'] = merge($collected_params.modifiers, $this.modifiers)
  $this['elements'] = merge($collected_params.elements, $this.elements)

  return $collected_params

stylobate_this()
  $this = {
    'parent': {}
    'arguments': ()
    'params': {}
    'modifiers': {}
    'elements': {}
    'urls': (),
    'selector': selector(),
    'selectors': ()
  }
  $null_collected = utilus_collect_by_type(())
  for $key in ('strings' 'idents' 'units' 'rgbas' 'booleans' 'functions' 'objects' 'arrays' 'colors')
    $this[$key] = $null_collected[$key]
  return $this

_stylobate_apply_defaults($this, $defaults)
  $origin = $this.called_from
  return false if not $origin

  $new_args = clone($defaults)

  $newthis = clone($this)
  $newthis['called_from'] = $origin + '_'
  $newthis['defaulted'] = true

  for $param, $index in $new_args
    unless $param == null
      $found_param = utilus_get($this['arguments'], utilus_ident($param[0]))
      if $found_param or (type($found_param) == 'string' and $found_param == '')
        $new_args[$index] = ''

  for $param, $index in $this.arguments
    $testparam = utilus_ident($param)
    $testparam = '' unless type($param) in ('ident' 'string' 'function' 'literal')
    unless match('^[-_]', $testparam)
      push($new_args, $param)

  $newthis['arguments'] = clone($new_args)
  _stylobate('', $origin + '_', $newthis)

_stylobate_collect_arguments($this)
  if length($this.parent)
    $this['params'] = $this['parent']['params']
    $this['modifiers'] = $this['parent']['modifiers']
    $this['elements'] = $this['parent']['elements']
    $this['urls'] = $this['parent']['urls']
    $this['selectors'] = $this['parent']['selectors']
  else
    $this['params'] = {}
    $this['modifiers'] = {}
    $this['elements'] = {}
    $this['urls'] = ()
    $this['selectors'] = ()

  // Remove the item's name from the arguments
  $this['arguments'][0] = unquote('')

  // Collect the arguments, first step
  $collected = utilus_collect_by_type($this['arguments'])
  for $key in ('strings' 'idents' 'units' 'rgbas' 'booleans' 'functions' 'objects' 'arrays' 'colors')
    $this[$key] = $collected[$key]

  // Getting different params and stuff
  if length($this['strings']) > 0
    for $key in $this['strings']
      // replace those checks with functions
      if match('\.(png|jpg|svg|gif)\b|https?:\/\/|^data:', $key)
        push($this['urls'], $key)
      else if match($selector_regex, $key)
        push($this['selectors'], $key)
      else if $key != ''
        // workaround for strange clone? bug
        $workaround = $this['params']
        $workaround[$key] = $key

  if length($this['idents']) > 0
    for $key in $this['idents']
      // workaround for strange clone? bug
      $workaround = $this['params']
      $workaround['' + utilus_ident($key)] = utilus_ident($key)

      if match('^(__|-[^\-])', $key)
        $workaround = $this['elements']
        $workaround[replace('^([_-]+)', '', '' + utilus_ident($key))] = '&'
      else if match('^(_|--)', $key)
        $workaround = $this['modifiers']
        $workaround[replace('^([_-]+)', '', '' + utilus_ident($key))] = '&'

  if length($this['functions']) > 0
    for $key in $this['functions']
      // workaround for strange clone? bug
      $workaround = $this['params']
      $workaround['' + utilus_ident($key)] = utilus_ident($key)

      if match('^(__|-[^\-])', utilus_ident($key))
        $workaround = $this['elements']
        $workaround[replace('^([_-]+)', '', '' + utilus_ident($key))] = '&'
      else if match('^(_|--)', utilus_ident($key))
        $workaround = $this['modifiers']
        $workaround[replace('^([_-]+)', '', '' + utilus_ident($key))] = '&'

  if length($this['arrays']) > 0
    for $index, $array in $this['arrays']
      $key = utilus_ident($array[0])

      $rest = ()
      for $item, $index in $array
        $rest = utilus_append($rest, $item) if $index > 0

      $workaround = $this['params']
      $workaround['' + $key] = $rest if $rest != ()

      if match('^(__|-[^\-])', $key) and $rest != ()
        $workaround = $this['elements']
        $workaround[replace('^([_-]+)', '', '' + $key)] = utilus_ident($rest)
      else if match('^(_|--)', $key) and $rest != ()
        $workaround = $this['modifiers']
        $workaround[replace('^([_-]+)', '','' + $key)] = utilus_ident($rest)


_stylobate($name, $params, $this = {
    parent: {},
    arguments: clone($params),
    called_from: $name + $params[0],
    selector: selector()
  })

  _stylobate_collect_arguments($this) unless $this.defaulted

  // Check if the called stylobate extends anything, if so, call it first,
  // and use the extended stylobate for subitems.
  $skin_name = utilus_ident($params[0])
  $extended_name_ending = replace('.*[^-_]+', '', $skin_name)
  $extended_name = $stylobate_extends[$name + $skin_name] || $stylobate_extends[$name + replace('[_-]+$', '', $skin_name)]
  $extended_name = utilus_ident($extended_name) + $extended_name_ending if $extended_name
  if $extended_name
    $this['is_extended'] = true
    $skin_name = replace('^' + $name, '', $extended_name)

    if !utilus_is_function($extended_name) and $extended_name_ending == ''
      error('No stylobate function “' + $extended_name + '”')

    stylobate_params($this, $stylobate_defaults[$extended_name]) if $extended_name in $stylobate_defaults and !($name + utilus_ident($params[0]) in $stylobate_extends_without_defaults)

  {$extended_name}($this) if $extended_name and $extended_name_ending == ''

  if $extended_name
    _stylobate_apply_defaults($this, $stylobate_defaults[$extended_name]) if $extended_name in $stylobate_defaults and !($name + utilus_ident($params[0]) in $stylobate_extends_without_defaults)

  if ($name + join('',$params) != '') and !utilus_is_function($name + utilus_ident($params[0])) and !$extended_name and !match('[_-]$', utilus_ident($params[0]))
    error('No such “' + $name + '” — “' + utilus_ident($params[0]) + '”')

  stylobate_params($this, $stylobate_defaults[$name + utilus_ident($params[0])]) if ($name + utilus_ident($params[0])) in $stylobate_defaults

  {$name + utilus_ident($params[0])}($this) if utilus_is_function($name + utilus_ident($params[0]))

  _stylobate_apply_defaults($this, $stylobate_defaults[$name + utilus_ident($params[0])]) if ($name + utilus_ident($params[0])) in $stylobate_defaults

  for $subitem in $this.arguments
    // There shouldn't be an `if` inside a {$selector}, 'cause
    // there is a crazy stylus bug. When it would be fixed,
    // revert this version to 669082fb35
    $selector = ''
    $foundsubitem = ''
    if is_array($subitem)
      if type($subitem[0]) in ('ident' 'string' 'function' 'literal')
        $testsubitem = utilus_ident($subitem[0])
        if match('^(\-|_)', $testsubitem)
          $foundsubitem = $subitem
          if type($subitem[1]) == 'string'
            if match($selector_regex, $subitem[1])
              $selector = $subitem[1]
            else
              $foundsubitem = ''
    else
      if type($subitem) in ('ident' 'string' 'function' 'literal')
        if match('^(\-|_)', utilus_ident($subitem))
          $foundsubitem = utilus_ident($subitem)

    call_subitem()
      $this_subitem = {
        parent: $this,
        arguments: clone($foundsubitem),
        selector: selector()
      }

      _stylobate_collect_arguments($this_subitem)

      $subitemname_prefix = ($extended_name ? '' : $name) +  replace('[_-]+$', '', $extended_name ? $extended_name : $skin_name)
      $subitemname_connector = replace('^([_-]+)[^_-].*$', '$1', utilus_ident($foundsubitem[0]))
      $subitemname_connector_substitutes = {
        '_': '--',
        '--': '_',
        '__': '-',
        '-': '__'
      }
      $subitemname_postfix = utilus_ident(replace('^[_-]+', '', utilus_ident($foundsubitem[0])))
      $subitemname = $subitemname_prefix + $subitemname_connector + $subitemname_postfix
      $subitemname = '' unless utilus_is_function($subitemname)
      if $subitemname == ''
        $subitemname_connector = $subitemname_connector_substitutes['' + $subitemname_connector]
        $subitemname = $subitemname_prefix + $subitemname_connector + $subitemname_postfix
        $subitemname = '' unless utilus_is_function($subitemname)
        if $stylobate_options.warn_on_incorrect_notation
          warn('' + 'Incorrect notation for %s, using %s.' % ($subitemname_prefix ($subitemname_connector + $subitemname_postfix))) unless $subitemname == ''

      // If there is extended subitem function, use it, otherwise
      $subitemname_extended = ''
      $subitemname_extended = $name + replace('[_-]+$', '', utilus_ident($params[0])) + utilus_ident($foundsubitem[0]) if $extended_name
      $subitemname_extended = '' unless utilus_is_function($subitemname_extended)


      // Push proper `called_from`
      $this_subitem['called_from'] = $subitemname unless $subitemname_extended or !$subitemname
      $this_subitem['called_from'] = $subitemname_extended if $subitemname_extended

      stylobate_params($this, $stylobate_defaults[$subitemname]) if $subitemname in $stylobate_defaults and !$subitemname_extended
      {$subitemname}($this_subitem) unless $subitemname_extended or !$subitemname
      _stylobate_apply_defaults($this_subitem, $stylobate_defaults[$subitemname]) if $subitemname in $stylobate_defaults and !$subitemname_extended

      stylobate_params($this, $stylobate_defaults[$subitemname_extended]) if $subitemname_extended in $stylobate_defaults and $subitemname_extended
      {$subitemname_extended}($this_subitem) unless !$subitemname_extended
      _stylobate_apply_defaults($this_subitem, $stylobate_defaults[$subitemname_extended]) if $subitemname_extended in $stylobate_defaults and $subitemname_extended

    $selector = '' if $selector == '&'
    {stylobate-selector($this, $selector)}
      call_subitem() if $foundsubitem != '' and $selector != ''

    if $selector == '' and $foundsubitem != ''
      call_subitem()

stylobate_property($name, $params)
  if utilus_is_comma_list($params)
    for $item in $params
      _stylobate($name, $item)
  else
    _stylobate($name, $params)
