# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.

require "protocol/url"
require "json"

describe Protocol::URL::Relative do
	it "creates a relative URL" do
		url = Protocol::URL::Relative.new("/_components/")
		expect(url.path).to be == "/_components/"
	end
	
	it "concatenates with another relative path" do
		base = Protocol::URL::Relative.new("/_components/")
		other = Protocol::URL::Relative.new("button.js")
		result = base + other
		expect(result).to be_a(Protocol::URL::Relative)
		expect(result.to_s).to be == "/_components/button.js"
	end
	
	it "simplifies .. in base path" do
		base = Protocol::URL["bar/.."]
		relative = Protocol::URL["baz/file.txt"]
		result = base + relative
		expect(result.to_s).to be == "baz/file.txt"
	end
	
	with "#+" do
		it "returns Absolute when adding Absolute to Relative" do
			relative = Protocol::URL::Relative.new("/path")
			absolute = Protocol::URL::Absolute.new("https", "//example.com", "/other")
			result = relative + absolute
			expect(result).to be_equal(absolute)
		end
		
		it "handles String argument" do
			relative = Protocol::URL::Relative.new("/base/")
			result = relative + "path.html"
			expect(result.path).to be == "/base/path.html"
		end
		
		it "raises error for invalid type" do
			relative = Protocol::URL::Relative.new("/path")
			expect do
				relative + 123
			end.to raise_exception(ArgumentError, message: be =~ /Cannot combine/)
		end
	end
	
	with "#query?" do
		it "returns true when query is present" do
			url = Protocol::URL::Relative.new("/path", "q=test")
			expect(url).to be(:query?)
		end
		
		it "returns false when query is nil" do
			url = Protocol::URL::Relative.new("/path", nil)
			expect(url).not.to be(:query?)
		end
		
		it "returns false when query is empty" do
			url = Protocol::URL::Relative.new("/path", "")
			expect(url).not.to be(:query?)
		end
	end
	
	with "#fragment?" do
		it "returns true when fragment is present" do
			url = Protocol::URL::Relative.new("/path", nil, "section")
			expect(url).to be(:fragment?)
		end
		
		it "returns false when fragment is nil" do
			url = Protocol::URL::Relative.new("/path", nil, nil)
			expect(url).not.to be(:fragment?)
		end
		
		it "returns false when fragment is empty" do
			url = Protocol::URL::Relative.new("/path", nil, "")
			expect(url).not.to be(:fragment?)
		end
	end
	
	with "#with" do
		it "updates path" do
			base = Protocol::URL::Relative.new("/api/users")
			updated = base.with(path: "groups")
			expect(updated.path).to be == "/api/groups"
		end
		
		it "updates query" do
			base = Protocol::URL::Relative.new("/search", "q=ruby")
			updated = base.with(query: "q=python")
			expect(updated.query).to be == "q=python"
		end
		
		it "updates fragment" do
			base = Protocol::URL::Relative.new("/docs", nil, "intro")
			updated = base.with(fragment: "advanced")
			expect(updated.fragment).to be == "advanced"
		end
		
		it "preserves existing values when not specified" do
			base = Protocol::URL::Relative.new("/path", "q=test", "section")
			updated = base.with(path: "other")
			expect(updated.query).to be == "q=test"
			expect(updated.fragment).to be == "section"
		end
	end
	
	with "#to_ary" do
		it "returns array representation" do
			url = Protocol::URL::Relative.new("/path", "q=test", "section")
			expect(url.to_ary).to be == ["/path", "q=test", "section"]
		end
	end
	
	with "#hash" do
		it "returns hash based on components" do
			url1 = Protocol::URL::Relative.new("/path", "q=test", "section")
			url2 = Protocol::URL::Relative.new("/path", "q=test", "section")
			expect(url1.hash).to be == url2.hash
		end
	end
	
	with "#<=>" do
		it "compares URLs" do
			url1 = Protocol::URL::Relative.new("/a")
			url2 = Protocol::URL::Relative.new("/b")
			expect(url1 <=> url2).to be == -1
			expect(url2 <=> url1).to be == 1
			expect(url1 <=> url1).to be == 0
		end
	end
	
	with "#==" do
		it "compares structural equality" do
			url1 = Protocol::URL::Relative.new("/path", "q=test", "section")
			url2 = Protocol::URL::Relative.new("/path", "q=test", "section")
			expect(url1).to be == url2
		end
		
		it "returns false for different URLs" do
			url1 = Protocol::URL::Relative.new("/path")
			url2 = Protocol::URL::Relative.new("/other")
			expect(url1).not.to be == url2
		end
	end
	
	with "#===" do
		it "compares string representations" do
			url = Protocol::URL::Relative.new("/path", "q=test")
			expect(url === "/path?q=test").to be == true
		end
		
		it "allows string to match URL in case statements" do
			path = "/docs"
			url = Protocol::URL::Relative.new("/docs")
			
			result = case path
			when url
				:match
			else
				:no_match
			end
			
			expect(result).to be == :match
		end
	end
	
	with "#as_json" do
		it "returns string representation" do
			url = Protocol::URL::Relative.new("/path", "q=test", "section")
			expect(url.as_json).to be == "/path?q=test#section"
		end
	end
	
	with "#to_json" do
		it "returns JSON string representation" do
			url = Protocol::URL::Relative.new("/path", "q=test", "section")
			expect(url.to_json).to be == '"/path?q=test#section"'
		end
	end
	
	with "#normalize!" do
		it "removes dot segments" do
			url = Protocol::URL::Relative.new("/foo/./bar")
			url.normalize!
			expect(url.path).to be == "/foo/bar"
		end
		
		it "resolves parent directory segments" do
			url = Protocol::URL::Relative.new("/foo/bar/../baz")
			url.normalize!
			expect(url.path).to be == "/foo/baz"
		end
		
		it "collapses multiple slashes" do
			url = Protocol::URL::Relative.new("/foo//bar///baz")
			url.normalize!
			expect(url.path).to be == "/foo/bar/baz"
		end
		
		it "handles complex paths" do
			url = Protocol::URL::Relative.new("/foo//bar/./baz/../qux")
			url.normalize!
			expect(url.path).to be == "/foo/bar/qux"
		end
		
		it "preserves trailing slash" do
			url = Protocol::URL::Relative.new("/foo/bar/")
			url.normalize!
			expect(url.path).to be == "/foo/bar/"
		end
		
		it "handles relative paths" do
			url = Protocol::URL::Relative.new("foo/bar/../baz")
			url.normalize!
			expect(url.path).to be == "foo/baz"
		end
		
		it "returns self" do
			url = Protocol::URL::Relative.new("/foo/bar")
			result = url.normalize!
			expect(result).to be_equal(url)
		end
		
		it "preserves query and fragment" do
			url = Protocol::URL::Relative.new("/foo//bar", "q=test", "section")
			url.normalize!
			expect(url.path).to be == "/foo/bar"
			expect(url.query).to be == "q=test"
			expect(url.fragment).to be == "section"
		end
	end
	
	with "#to_local_path" do
		it "converts path to local file system path" do
			url = Protocol::URL::Relative.new("/documents/report.pdf")
			expect(url.to_local_path).to be == "/documents/report.pdf"
		end
		
		it "handles percent-encoded characters" do
			url = Protocol::URL::Relative.new("/files/My%20Document.txt")
			expect(url.to_local_path).to be == "/files/My Document.txt"
		end
		
		it "handles unicode characters" do
			url = Protocol::URL::Relative.new("/files/%E2%9D%A4%EF%B8%8F.txt")
			expect(url.to_local_path).to be == "/files/❤️.txt"
		end
		
		it "preserves encoded path separators" do
			url = Protocol::URL::Relative.new("/safe%2Fname/file.txt")
			expect(url.to_local_path).to be == "/safe%2Fname/file.txt"
		end
	end
end
