# frozen_string_literal: true

module Bridgetown
  module Model
    class RepoOrigin < Origin
      using Bridgetown::Refinements
      include Bridgetown::FrontMatter::Importer

      # @return [String]
      attr_accessor :content

      # @return [Integer]
      attr_accessor :front_matter_line_count

      class << self
        def handle_scheme?(scheme)
          scheme == "repo"
        end

        def data_file_extensions
          %w(.yaml .yml .json .csv .tsv .rb).freeze
        end

        # Initializes a new repo object using a collection and a relative source path.
        # You'll need to use this when you want to create and save a model to the source.
        #
        # @param collection [Bridgetown::Collection, String, Symbol] either a collection
        #   label or Collection object
        # @param relative_path [Pathname, String] the source path of the file to save
        def new_with_collection_path(collection, relative_path, site: Bridgetown::Current.site)
          collection = collection.label if collection.is_a?(Bridgetown::Collection)

          new("repo://#{collection}.collection/#{relative_path}", site:)
        end
      end

      def read
        begin
          @data = (in_data_collection? ? read_file_data : read_front_matter(original_path)) || {}
        rescue SyntaxError => e
          Bridgetown.logger.error "Error:",
                                  "Ruby Exception in #{e.message}"
        rescue StandardError => e
          handle_read_error(e)
        end

        @data ||= {}
        @data[:_id_] = id
        @data[:_origin_] = self
        @data[:_collection_] = collection
        @data[:_content_] = content if content

        @data
      end

      def write(model)
        if File.exist?(original_path) && !Bridgetown::Utils.has_yaml_header?(original_path)
          raise Bridgetown::Errors::InvalidYAMLFrontMatterError,
                "Only existing files containing YAML front matter can be overwritten by the model"
        end

        contents = "#{front_matter_to_yaml(model)}---\n\n#{model.content}"

        # Create folders if necessary
        dir = File.dirname(original_path)
        FileUtils.mkdir_p(dir) unless File.directory?(dir)

        File.write(original_path, contents)

        true
      end

      def url
        @url ||= URI.parse(id)
      end

      def relative_path
        @relative_path ||= Pathname.new(
          Addressable::URI.unescape(url.path.delete_prefix("/"))
        )
      end

      def collection
        return @collection if @collection

        collection_name = url.host.ends_with?(".collection") ?
                            url.host.chomp(".collection") :
                            "pages"
        @collection = site.collections[collection_name]
      end

      def original_path
        @original_path ||= relative_path.expand_path(site.source)
      end

      def exists?
        File.exist?(original_path)
      end

      private

      def in_data_collection?
        original_path.extname.downcase.within?(self.class.data_file_extensions) &&
          collection.data?
      end

      def read_file_data # rubocop:todo Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
        case original_path.extname.downcase
        when ".csv"
          {
            rows:
              CSV.read(original_path,
                       headers: true,
                       encoding: site.config["encoding"]).map(&:to_hash),
          }
        when ".tsv"
          {
            rows:
              CSV.read(original_path,
                       col_sep: "\t",
                       headers: true,
                       encoding: site.config["encoding"]).map(&:to_hash),
          }
        when ".rb"
          Bridgetown::Utils::RubyExec.process_ruby_data(
            self,
            File.read(original_path),
            original_path,
            1
          )
        when ".json"
          json_data = JSON.parse(File.read(original_path))
          json_data.is_a?(Array) ? { rows: json_data } : json_data
        else
          yaml_data = YAMLParser.load_file(original_path)
          yaml_data.is_a?(Array) ? { rows: yaml_data } : yaml_data
        end
      end

      def handle_read_error(error)
        if error.is_a? Psych::SyntaxError
          Bridgetown.logger.error "Error:",
                                  "YAML Exception reading #{original_path}: #{error.message}"
        else
          Bridgetown.logger.error "Error:",
                                  "could not read file #{original_path}: #{error.message}"
        end

        if site.config["strict_front_matter"] ||
            error.is_a?(Bridgetown::Errors::FatalException)
          raise error
        end
      end

      def front_matter_to_yaml(model)
        data = model.data_attributes.to_h
        data = data.deep_merge(data) do |_, _, v|
          case v
          when DateTime
            v.to_time
          when Symbol
            v.to_s
          else
            v
          end
        end
        data.each do |k, v|
          data.delete(k) if v.nil?
        end

        data.to_yaml
      end
    end
  end
end
