module Csvlint
  class Schema
    include Csvlint::ErrorCollector

    attr_reader :uri, :fields, :title, :description

    def initialize(uri, fields = [], title = nil, description = nil)
      @uri = uri
      @fields = fields
      @title = title
      @description = description
      reset
    end

    class << self
      extend Gem::Deprecate

      def from_json_table(uri, json)
        fields = []
        json["fields"]&.each do |field_desc|
          fields << Csvlint::Field.new(field_desc["name"], field_desc["constraints"],
            field_desc["title"], field_desc["description"])
        end
        Schema.new(uri, fields, json["title"], json["description"])
      end

      def from_csvw_metadata(uri, json)
        Csvlint::Csvw::TableGroup.from_json(uri, json)
      end

      # Deprecated method signature
      def load_from_json(uri, output_errors = true)
        load_from_uri(uri, output_errors)
      end
      deprecate :load_from_json, :load_from_uri, 2018, 1

      def load_from_uri(uri, output_errors = true)
        load_from_string(uri, URI.open(uri).read, output_errors)
      rescue OpenURI::HTTPError, Errno::ENOENT => e
        raise e
      end

      def load_from_string(uri, string, output_errors = true)
        json = JSON.parse(string)
        if json["@context"]
          uri = "file:#{File.expand_path(uri)}" unless /^http(s)?/.match?(uri.to_s)
          Schema.from_csvw_metadata(uri, json)
        else
          Schema.from_json_table(uri, json)
        end
      rescue TypeError
        # NO IDEA what this was even trying to do - SP 20160526
      rescue Csvlint::Csvw::MetadataError => e
        raise e
      rescue => e
        if output_errors === true
          warn e.class
          warn e.message
          warn e.backtrace
        end
        Schema.new(nil, [], "malformed", "malformed")
      end
    end

    def validate_header(header, source_url = nil, validate = true)
      reset

      found_header = header.to_csv(row_sep: "")
      expected_header = @fields.map { |f| f.name }.to_csv(row_sep: "")
      if found_header != expected_header
        build_warnings(:malformed_header, :schema, 1, nil, found_header, "expectedHeader" => expected_header)
      end
      valid?
    end

    def validate_row(values, row = nil, all_errors = [], source_url = nil, validate = true)
      reset
      if values.length < fields.length
        fields[values.size..-1].each_with_index do |field, i|
          build_warnings(:missing_column, :schema, row, values.size + i + 1)
        end
      end
      if values.length > fields.length
        values[fields.size..-1].each_with_index do |data_column, i|
          build_warnings(:extra_column, :schema, row, fields.size + i + 1)
        end
      end

      fields.each_with_index do |field, i|
        value = values[i] || ""
        field.validate_column(value, row, i + 1, all_errors)
        @errors += fields[i].errors
        @warnings += fields[i].warnings
      end

      valid?
    end
  end
end
