/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;

import static de.srsoftware.umbrella.core.Util.mapMarkdown;
import static de.srsoftware.umbrella.core.constants.Constants.JSONOBJECT;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Text.BOOLEAN;
import static de.srsoftware.umbrella.core.constants.Text.STRING;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Translatable.t;
import static java.lang.String.join;

import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.api.UserService;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import org.json.JSONObject;

public class WikiPage implements Mappable {

	private final long id;
	private String title;
	private int version;
	private final Set<Integer> versions = new TreeSet<>();
	private final Map<Long,Member> members = new HashMap<>();
	private String content;
	private Set<String> dirtyFields = new HashSet<>();
	private boolean guestAllowed = false;

	public WikiPage(long id, String title, int version, String content) {
		this.id = id;
		this.version = version;
		this.content = content;
		this.title   = title;
	}

	public String content(){
		return content;
	}

	private WikiPage content(String newVal) {
		if (content.equals(newVal)) return this;
		content = newVal;
		version++;
		dirtyFields.add(CONTENT);
		return this;
	}

	public Set<String> dirtyFields(){
		return Set.copyOf(dirtyFields);
	}

	public boolean guestAllowed() {
		return guestAllowed;
	}

	public void guestAllowed(boolean guestAllowed) {
		this.guestAllowed = guestAllowed;
		dirtyFields.add(GUEST_ALLOWED);
	}

	public long id(){
		return id;
	}

	public boolean isDirty(String field) {
		return dirtyFields.contains(field);
	}

	public Map<Long,Member> members(){
		return members;
	}

	public static WikiPage of(ResultSet rs) throws SQLException {
		return new WikiPage(rs.getLong(ID), rs.getString(TITLE),rs.getInt(VERSION),rs.getString(CONTENT));
	}

	public WikiPage patch(JSONObject json, UserService users) {
		for (var key : json.keySet()){
			var val = json.get(key);
			switch (key){
				case ID:
					throw unprocessable("{field} cannot be altered!", FIELD, ID);
				case CONTENT:
					if (!(val instanceof String s)) throw invalidField(CONTENT,t(STRING));
					content(s);
					break;
				case GUEST_ALLOWED:
					if (!(val instanceof Boolean b)) throw invalidField(GUEST_ALLOWED, t(BOOLEAN));
					guestAllowed(b);
					break;
				case MEMBERS:
					if (!(val instanceof JSONObject membersJson)) throw invalidField(MEMBERS,t(JSONOBJECT));
					for (var uid   : membersJson.keySet()){
						var userId = Long.parseLong(uid);
						var user   = users.loadUser(userId);
						if (!(membersJson.get(uid) instanceof JSONObject memberJSON)) throw invalidField(MEMBERS +"."+uid,t(JSONOBJECT));
						var p = memberJSON.get(PERMISSION);
						if (p == JSONObject.NULL){
							members.remove(userId);
							dirtyFields.add(MEMBERS);
							break;
						}
						if (!(p instanceof JSONObject perm)) throw invalidField(join(".", MEMBERS,uid, PERMISSION),t(JSONOBJECT));
						if (!(perm.get(NAME)             instanceof String permName)) throw invalidField(join(".", MEMBERS,uid, PERMISSION, NAME),t(STRING));
						var permission = Permission.valueOf(permName);
						var member     = new Member(user,permission);
						var existing   = members.get(userId);
						if (existing  == null || existing.permission() != permission) {
							members.put(userId, member);
							dirtyFields.add(MEMBERS);
						}
					}
					break;
				case TITLE:
					if (!(val instanceof String t)) throw invalidField(TITLE,t(STRING));
					title(t);
					break;
			}
		}
		return this;
	}

    public WikiPage setNew(){
        dirtyFields.add(TITLE);
        dirtyFields.add(CONTENT);
        dirtyFields.add(MEMBERS);
        return this;
    }

	public String title(){
		return title;
	}

	private WikiPage title(String newVal) {
		if (title.equals(newVal)) return this;
		title = newVal;
		version++;
		dirtyFields.add(TITLE);
		return this;
	}

	@Override
	public Map<String, Object> toMap() {
		var memberMap = new HashMap<Long,Map<String,Object>>();
		for (var entry : members.entrySet()) memberMap.put(entry.getKey(),entry.getValue().toMap());

		return Map.of(
				ID,id,
				CONTENT,mapMarkdown(content),
				GUEST_ALLOWED,guestAllowed,
				MEMBERS,memberMap,
				TITLE,title,
				VERSION,version,
				VERSIONS,versions
		);
	}

	@Override
	public String toString() {
		return title;
	}

	public int version(){
		return version;
	}

	public Set<Integer> versions(){
		return versions;
	}
}
