module Octodmin
  class Post
    attr_accessor :post

    ATTRIBUTES_FOR_SERIALIZAION = Jekyll::Post::ATTRIBUTES_FOR_LIQUID - %w[
      previous
      next
    ]

    def self.find(id)
      site = Octodmin::Site.new
      site.posts.find { |post| post.identifier == id }
    end

    def self.create(options = {})
      site = Octodmin::Site.new

      # Prepare slug
      options["slug"] = slug_for(site, options[:title])

      # Create post
      post = Octopress::Post.new(Octopress.site, Jekyll::Utils.stringify_hash_keys(options))
      post.write

      site.posts.first
    rescue RuntimeError
    end

    def initialize(site, post)
      @site = site
      @post = post
    end

    def path
      @post.path
    end

    def identifier
      path.split("/").last.split(".").first
    end

    def serializable_hash
      hash = @post.to_liquid(ATTRIBUTES_FOR_SERIALIZAION).dup

      excerpt = Jekyll::Excerpt.new(@post)
      excerpt.do_layout({ "site" => @site.config }, {})

      hash.merge(
        excerpt: excerpt.content,
        identifier: identifier,
        slug: @post.slug,
        added: added?,
        changed: changed?,
        deleted: deleted?,
      )
    end

    def update(params)
      # Remove old post
      delete

      # Init the new one
      octopost = Octopress::Post.new(Octopress.site, {
        "path" => @post.path,
        "date" => params["date"],
        "slug" => params["slug"],
        "title" => params["title"],
        "force" => true,
      })

      options = Hash[@site.config["octodmin"]["front_matter"].keys.map do |key|
        [key, params[key]]
      end]

      content = params["content"].gsub("\r\n", "\n").strip
      result = "---\n#{options.map { |k, v| "#{k}: \"#{v}\"" }.join("\n")}\n---\n\n#{content}\n"
      octopost.instance_variable_set(:@content, result)
      octopost.write

      @post = post_for(octopost)

      # Add/reset post just in case
      git = Git.open(Octodmin::App.dir)
      git.add(octopost.path)
      git.reset(octopost.path)
    end

    def delete
      octopost = octopost_for(@post)
      git = Git.open(Octodmin::App.dir)
      git.lib.send(:command, "rm", ["-f", "--cached", octopost.path])
    rescue Git::GitExecuteError
      File.delete(octopost.path)
    end

    def restore
      return unless deleted?

      octopost = octopost_for(@post)
      git = Git.open(Octodmin::App.dir)
      git.add(octopost.path)

      @site.reset
      @post = post_for(octopost)
    end

    def revert
      return unless changed?

      octopost = octopost_for(@post)
      git = Git.open(Octodmin::App.dir)
      git.reset(octopost.path)
      git.checkout(octopost.path)

      @site.reset
      @post = post_for(octopost)
    end

    private

    def self.slug_for(site, title)
      title.to_slug.transliterate(
        site.config["octodmin"]["transliterate"]
      ).normalize.to_s
    end

    def octopost_for(post)
      Octopress::Post.new(Octopress.site, {
        "path" => post.path,
        "date" => post.date,
        "slug" => post.slug,
        "title" => post.title,
      })
    end

    def post_for(octopost)
      @site.posts.find do |post|
        File.join(@site.site.source, post.post.path) == octopost.path
      end.post
    end

    def has_status?(post, status)
      path = File.join(@site.source, post.path)
      changed = @site.status.public_send(status).keys
      paths = changed.map { |path| File.join(Octodmin::App.dir, path) }
      paths.include?(path)
    end

    def added?
      has_status?(@post, :untracked) && !changed? && !deleted?
    end

    def changed?
      has_status?(@post, :changed)
    end

    def deleted?
      has_status?(@post, :deleted)
    end
  end
end
