# frozen_string_literal: true

# rubocop:disable Style/PerlBackrefs -- to simplify

module Junoser
  class JsRuler
    def initialize(input)
      @rule = input
      @sequence = 0
    end

    def sequence
      @sequence += 1
    end

    def to_rule
      +rule_header << rule.gsub(/^/, '  ') << rule_footer
    end

    def rule
      str = @rule.read
      str = process_lines(str)
      str = str.split("\n").map do |l|
        process_line(l) unless l =~ /^ *#/ # Skip additional comment lines
      end.compact.join("\n")
      finalize(str)
    end

    private

    def process_lines(str)
      # set groups
      str.gsub!(/"groups" \(.*\s*s\(\s*any\s*\)\s*\)/, <<-REPLACE.strip)
          "groups" arg (  /* Configuration groups */
            c(
              this.configuration({ groups: false }),
              "when" (  /* Specify additional conditions for groups */
                c(
                  "chassis" arg  /* Chassis id */,
                  "member" arg  /* Member of virtual chassis */,
                  "model" arg  /* Model name */,
                  "node" arg  /* Node of cluster */,
                  "peers" arg  /* Hosts on which this group should be effective */,
                  "routing-engine" arg  /* Routing Engine */,
                  "time" (  /* Time at which group should be effective */
                    "to" arg  /* End time([yyyy-mm-dd.]hh:mm) */,
                    arg
                  )
                )
              )
            )
          )
      REPLACE

      # set protocols mpls path
      str.gsub!(/("path" arg \(.*Route of a label-switched path.*)(\s*)c\(/) do
        "#{$1}#{$2}s(#{$2}ipaddr,"
      end

      # set protocols iccp peer xxx liveness-detection single-hop
      str.gsub!(/(^rule\(:peer_group\) do.*?\n(\s*)"detection-time" \(.*?c\(\s*"threshold" arg .*?\)\s*\))/m) do
        %(#{$1},\n#{$2}"single-hop")
      end

      # set forwarding-options dhcp-relay server-group
      str.gsub!(/^(rule\(:(?:v6_)?server_group_type\) do)\n(.*?)\nend/m) do
        "#{$1}\n  arg.as(:arg) (\n#{$2}\n  )\nend"
      end

      # set interfaces xxx enable
      str.gsub!(/^(rule\(:interfaces_type\) do\s*[^\n]*\s*c\()(\s*)/m) do
        %(#{$1}#{$2}"enable",#{$2})
      end

      str
    end

    def process_line(str)
      str = remove_undefined_variables(str)
      str = process_common_syntax(str)
      str = process_argument_syntax(str)
      str = process_structural_syntax(str)
      str = process_word(str)
      str = process_reserved_word(str)
      process_comment(str)
    end

    def remove_undefined_variables(str)
      str.gsub! '.as(:oneline)', ''

      # "$junos-interface-unit" |
      str.gsub!(/"\$[\w-]+" \| /, '')

      str
    end

    def process_common_syntax(str)
      # rule(:foo) do  ->  foo(opts) {
      str.gsub!(/^rule\(:(\S+)\) do/) { "#{$1}(opts) { return_next_line" }
      # end  ->  }
      str.gsub!(/^(\s*)end$/) { "#{$1}}" }

      # arg.as(:arg) (  ->  {"arg":
      str.gsub!('arg.as(:arg) (', '{"arg":')
      # arg.as(:arg)  ->  {"arg": null}
      str.gsub!(/arg.as\(:arg\)/, '{"arg": null}')
      # ("foo" | "bar").as(:arg) (  ->  {"(bar|baz)":
      str.gsub!(/\(([^)]+)\)\.as\(:arg\) \(/) { "{\"(#{$1.gsub('"', '').split(' | ').join('|')})\":" }
      # enum(("foo" | "bar")).as(:arg) (  ->  {"(bar|baz)":  # TODO: support "enum" in the middle of line
      str.gsub!(/enum\(\(([^)]+)\)\)\.as\(:arg\) \(/) { "{\"(#{$1.gsub('"', '').split(' | ').join('|')})\":" }

      str.gsub! '.as(:arg)', ''

      # c(  ->  {
      str.gsub!(/^(\s*)c\(/) { "#{$1}{" }

      # foo  /* doc */,  ->  foo()  /* doc */,
      str.gsub!(%r{^(\s*)(?!arg)(\w+)(  /\* (.*) \*/,?)$}) { "#{$1}this.#{$2}()#{$3}" }
      # foo,  ->  foo(),
      str.gsub!(/^(\s*)(?!arg)(\w+)(,?)$/) { "#{$1}this.#{$2}()#{$3}" }

      # )  ->  }
      str.gsub!(/^(\s+)\)/) { "#{$1}}" }

      str
    end

    def process_argument_syntax(str)
      # "foo" (("bar" | "baz")) (  ->  "foo(bar|baz)": {
      # "foo" enum(("bar" | "baz")) (  ->  "foo(bar|baz)": {  # TODO: support "enum" in the middle of line
      str.gsub!(/^(\s*)"(\S+)" (?:enum)?\(\((.*)\)\) \(/) do
        "#{$1}\"#{$2}(#{$3.gsub('"', '').split(' | ').join('|')})\": {"
      end
      # "foo" arg (  ->  "foo(arg)": {
      str.gsub!(/^(\s*)"(\S+)" arg \(/) { "#{$1}\"#{$2}(arg)\": {" }

      # "foo" (... | arg)  /* doc */  ->  "foo": "arg"
      str.gsub!(%r{^(\s*)"(\S+)" \(.* \| arg ?.*\)  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2} | #{$3}\": arg#{$4}" }

      # "foo" (... | arg) (  /* doc */  ->  "foo(arg)": {
      str.gsub!(%r{^(\s*)"(\S+)" \(.* \| arg ?.*\) \(  /\* (.*) \*/$}) { "#{$1}\"#{$2}(arg) | #{$3}\": {" }
      # "foo" (... | arg) (  ->  "foo(arg)": {
      str.gsub!(/^(\s*)"(\S+)" \(.* \| arg ?.*\) \($/) { "#{$1}\"#{$2}(arg)\": {" }

      # "foo" (arg | ...)  /* doc */  ->  "foo": "arg"
      str.gsub!(%r{^(\s*)"(\S+)" \(arg ?.*\)  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2} | #{$3}\": arg#{$4}" }

      # "foo" (arg | ...) (  /* doc */  ->  "foo(arg)": {
      str.gsub!(%r{^(\s*)"(\S+)" \(arg ?.*\) \(  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2}(arg) | #{$3}\": {#{$4}" }

      # "foo" ("bar" | "baz") (  /* doc */  ->  "foo(bar|baz)": {
      str.gsub!(%r{^(\s*)"(\S+)" \("([^)]+)"\) \(  /\* (.*) \*/$}) do
        "#{$1}\"#{$2}(#{$3.split('" | "').join('|')}) | #{$4}\": {"
      end
      # "foo" ("bar" | "baz") (  ->  "foo(bar|baz)": {
      str.gsub!(/^(\s*)"(\S+)" \("([^)]+)"\) \($/) { "#{$1}\"#{$2}(#{$3.split('" | "').join('|')})\": {" }

      # "foo" ("bar" | "baz") /* doc */,  ->  "foo": ["bar", "baz"],
      str.gsub!(%r{^(\s*)"(\S+)" \(([^)]+)\)  /\* (.*) \*/(,?)$}) do
        "#{$1}\"#{$2} | #{$4}\": [#{$3.split(' | ').join(', ')}]#{$5}"
      end
      # "foo" ("bar" | "baz"),  ->  "foo": ["bar", "baz"],
      str.gsub!(/^(\s*)"(\S+)" \(([^)]+)\)(,?)$/) { "#{$1}\"#{$2}\": [#{$3.split(' | ').join(', ')}]#{$4}" }

      # "foo" enum(("bar" | "baz"))  ->  "foo": new Enumeration(["bar", "baz"])
      str.gsub!(/^(\s*)("\S+") enum\(\((.*)\)\)/) { "#{$1}#{$2}: new Enumeration(#{$3.gsub('"', '').split(' | ')})" }

      # "foo" arg  /* doc */,  ->  "foo | doc": "arg",
      str.gsub!(%r{^(\s*)"([^"]+)" arg  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2} | #{$3}\": \"arg\"#{$4}" }
      # "foo" arg,  ->  "foo": "arg",
      str.gsub!(/(\s*)"([^"]+)" arg(,?)$/) { "#{$1}\"#{$2}\": \"arg\"#{$3}" }

      # "foo" ipaddr,  -> "foo": this.ipaddr(),
      str.gsub!(/(\s*)"([^"]+)" ipaddr(,?)$/) { "#{$1}\"#{$2}\": this.ipaddr()#{$3}" }

      str
    end

    def process_structural_syntax(str)
      # "foo" (  /* doc */  ->  "foo | doc": (opts) => {
      str.gsub!(%r{^(\s*)"(\S+)" \(  /\* (.*) \*/}) { "#{$1}\"#{$2} | #{$3}\": {" }
      # "foo" (  ->  "foo": (opts) => {
      str.gsub!(/^(\s*)"(\S+)" \($/) { "#{$1}\"#{$2}\": {" }

      # "foo" arg (  /* doc */  ->  "foo | doc": (opts) => {
      str.gsub!(%r{^(\s*)"(\S+)" arg \(  /\* (.*) \*/}) { "#{$1}\"#{$2} | #{$3}\": {" }

      str
    end

    def process_word(str)
      # arg  ->  "arg"
      # (arg)  ->  "arg"
      # enum(arg)  ->  "arg"
      str.gsub!(/ ((?!arg\w)(arg)|(enum)?\(arg\))/, ' "arg"')

      # "foo"  /* doc */,  ->  "foo | doc": null,
      str.gsub!(%r{^(\s*)"([^"]+)"  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2} | #{$3}\": null#{$4}" }
      # "foo",  ->  "foo": null,
      str.gsub!(/^(\s*)"([^"]+)"(,?)$/) { "#{$1}\"#{$2}\": null#{$3}" }

      # ("foo" | "bar")  ->  ["foo", "bar"]
      str.gsub!(/^(\s*) \(+("[^"]+(?:" \| "[^"]+)*")\)+(,?)$/) do
        comma = $3
        "#{$1}#{$2.gsub('"', '').split(' | ')}#{comma}"
      end

      # enum(("bar" | "baz"))  ->  "foo": new Enumeration(["bar", "baz"])
      str.gsub!(/^(\s*)enum\(\((.*)\)\)/) { "#{$1}new Enumeration(#{$2.gsub('"', '').split(' | ')})" }

      # (arg | "foo")  ->  ["arg", "foo"]
      str.gsub!(/^(\s*) \(arg( \| "[^"]+")+\)/) { "#{$1}[\"arg\"#{$2.split(' | ').join(', ')}]" }

      str
    end

    def process_reserved_word(str)
      # ieee-802.3ad -> 802.3ad
      str.gsub! 'ieee-802.3ad', '802.3ad'

      # end-range -> to
      str.gsub! '"end-range"', '"to"'

      # "policy | Define a policy context from this zone" -> "from-zone | Define a policy context from this zone"
      str.gsub! 'policy | Define a policy context from this zone', 'from-zone | Define a policy context from this zone'
      # "to-zone-name | Destination zone" -> "to-zone | Destination zone"
      str.gsub! 'to-zone-name | Destination zone', 'to-zone | Destination zone'
      # "system-name | System name override" -> "name | System name override"
      str.gsub! 'system-name | System name override', 'name | System name override'

      # "set class-of-service interfaces all unit"
      str.gsub! '"unit(*)"', '"unit(*|arg)"'
      # "classifiers xxx"
      str.gsub! '["default"]', '["default", "arg"]'

      # keywords "dest-nat-rule-match", "src-nat-rule-match", "static-nat-rule-match" are wrong
      str.gsub!(/"(dest|src|static)-nat-rule-match/) { '"match' }

      # faster interface speed support
      str.gsub! '"400g", "oc3"', '"400g", "800g", "oc3"'
      str.gsub! '"400G"', '"400G", "800G"'

      # support "set interfaces xxx ether-options speed"
      str.gsub! '"ethernet-1', '"1'

      fix_route_filter(str)

      str
    end

    def fix_route_filter(str)
      str.gsub!(/("exact \| [^"]*"): "arg"/) { "#{$1}: null" }
      str.gsub!(/("longer \| [^"]*"): "arg"/) { "#{$1}: null" }
      str.gsub!(/("orlonger \| [^"]*"): "arg"/) { "#{$1}: null" }
    end

    def process_comment(str)
      # "foo": ...  /* doc */,  ->  "foo | doc": ...,
      str.gsub!(%r{^(\s*)"([^"]+)": (.*)  /\* (.*) \*/(,?)$}) { "#{$1}\"#{$2} | #{$4}\": #{$3}#{$5}" }

      str
    end

    def finalize(lines)
      lines = balance_parenthesis(lines)
      objectize_arg_of_s(lines)

      # return_next_line
      #   ...
      #
      # ->
      #
      # return ...
      lines.gsub!(/return_next_line\n(\s*)/m) { "\n#{$1}return " }

      # {
      #   {
      #
      # ->
      #
      # {
      #   "null_1": {
      lines.gsub!(/([{,]\n\s*){/m) { "#{$1}\"null_#{sequence}\": {" }

      # {
      #   foo()
      #
      # ->
      #
      # {
      #   "null_1": foo()
      lines.gsub!(/([{,]\n\s*)([^ "(]+)/m) { "#{$1}\"null_#{sequence}\": #{$2}" }

      # "arg"  ->  "arg_1"
      lines.gsub('"arg":') { %("arg_#{sequence}":) }

      conditional_groups(lines)
    end

    # }  ->  )
    def balance_parenthesis(lines)
      # Fixes
      lines.gsub! 'Timeslots (1..24;', 'Timeslots (1..24);'
      lines.gsub! '(such that the VLAN range is x <= range <= y.', '(such that the VLAN range is x <= range <= y).'
      lines.gsub! '(intended primarily for use with a peer or group"',
                  '(intended primarily for use with a peer or group)"'
      lines.gsub! '(e.g. QSFP28 and QSFP+,', '(e.g. QSFP28 and QSFP+),'
      lines.gsub! '(see the description of policy evaluation at the top of this module."',
                  '(see the description of policy evaluation at the top of this module)."'

      count = 0
      target = nil
      buf = +''
      stack = []

      lines.each_char do |c|
        case c
        when '('
          count += 1
          stack << target
          target = count

          buf << c
        when '{'
          count += 1

          buf << c
        when ')'
          count -= 1
          target = stack.pop

          buf << c
        when '}'
          count -= 1

          if target && count == target - 1
            target = stack.pop

            buf << ')'
          else
            buf << c
          end
        else
          buf << c
        end
      end

      buf
    end

    def objectize_arg_of_s(lines)
      # s(  ->  s({
      lines.gsub!(/^( *(?:return|\S+:)? +)s\($/m) { "#{$1}this.s({" }
      # sc(  ->  sc({
      lines.gsub!(/^( *(?:return|\S+:)? +)sc\($/m) { "#{$1}this.sc({" }

      # )  ->  })
      lines.gsub!(/^( *)\)(,?)$/m) { "#{$1}})#{$2}" }

      lines
    end

    def conditional_groups(lines)
      # "null_13264": this.configuration(false),
      # ->
      # (opts === undefined || opts?.configuration !== false) && { "null_13264": this.configuration(false) },
      #
      # ref: process_lines()
      lines.gsub(/("groups\(arg\) \| .*":) {/) do
        "#{$1} opts?.groups !== false && {"
      end
    end

    def rule_header
      <<~HEADER
        /* eslint-disable semi */

        class Enumeration {
          constructor(list) {
            this.list = list;
          }
        }

        class Repeatable {
          constructor(list) {
            this.list = list;
          }
        }

        class Sequence {
          constructor(list) {
            this.list = list;
          }

          get(depth) {
            return this.list[depth];
          }
        }

        class JunosSchema {
          any() {
            return "any";
          }

          s(obj) {
            const list = Object.entries(obj).map(([key, value]) => ({[key]: value}));
            return new Sequence(list);
          }

          sc(obj) {
            return new Repeatable(obj);
          }

      HEADER
    end

    def rule_footer
      <<~FOOTER

        }

        // eslint-disable-next-line no-undef
        module.exports = {JunosSchema, Enumeration, Repeatable, Sequence};
      FOOTER
    end
  end
end

# rubocop:enable Style/PerlBackrefs
