/**
 * This file is part of Radicale Server - Calendar Server
 * Copyright © 2026-2026 Max Berger <max@berger.name>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import { ROOT_PATH, SERVER } from "../constants.js";
import { CollectionType } from "../models/collection.js";
import { create_request, to_error_message } from "./common.js";

/**
 * @typedef {Object} SharingFeatures
 * @property {number} [ApiVersion]
 * @property {string} [Status]
 * @property {boolean} [FeatureEnabledCollectionByMap]
 * @property {boolean} [PermittedCreateCollectionByMap]
 * @property {boolean} [FeatureEnabledCollectionByToken]
 * @property {boolean} [PermittedCreateCollectionByToken]
 * @property {Array<string>} [SupportedConversions]
 */

/**
 * @typedef {Object} ServerFeatures
 * @property {SharingFeatures} [sharing]
 */

/**
 * @param {string} user
 * @param {?string} password
 * @param {string} path
 * @param {object} body
 * @param {function(string):void} on_success
 * @param {?function():void} on_not_found
 * @param {?function(string):void} on_error
 * @returns {XMLHttpRequest}
 */
function call_sharing_api(
    user,
    password,
    path,
    body,
    on_success,
    on_not_found = null,
    on_error = null,
) {
    let request = create_request(
        "POST",
        SERVER + ROOT_PATH + ".sharing/v1/" + path,
        user,
        password,
    );
    request.onreadystatechange = function () {
        if (request.readyState !== 4) {
            return;
        }
        if (200 <= request.status && request.status < 300) {
            on_success(request.responseText);
        } else if (request.status === 404) {
            if (on_not_found) {
                on_not_found();
            } else if (on_error) {
                on_error("Not found");
            } else {
                console.error("Not found");
            }
        } else {
            if (on_error) {
                on_error(to_error_message(request));
            } else {
                console.error(to_error_message(request));
            }
        }
    };
    request.setRequestHeader("Accept", "application/json");
    request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    request.send(body ? JSON.stringify(body) : null);
    return request;
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {function(import("../api/sharing.js").ServerFeatures, ?string):void} callback
 */
export function discover_server_features(user, password, callback) {
    call_sharing_api(
        user,
        password,
        "all/info",
        {},
        function (response) {
            try {
                let features = { "sharing": JSON.parse(response) };
                callback(features, null);
            } catch (e) {
                if (e instanceof Error) {
                    callback({}, e.message);
                } else {
                    callback({}, e ? e.toString() : "Unknown error");
                }
            }
        },
        function () {
            // sharing is disabled on the server
            callback({ "sharing": {} }, null);
        },
        function (error) {
            callback({}, error);
        },
    );
}

/**
 * @typedef {Object} ShareData
 * @property {string} [ShareType]
 * @property {string} [PathOrToken]
 * @property {string} [PathMapped]
 * @property {string} [Owner]
 * @property {string} [User]
 * @property {string} [Permissions]
 * @property {?boolean} [EnabledByOwner]
 * @property {?boolean} [EnabledByUser]
 * @property {?boolean} [HiddenByOwner]
 * @property {?boolean} [HiddenByUser]
 * @property {number} [TimestampCreated]
 * @property {number} [TimestampUpdated]
 * @property {Object<String, String>} [Properties]
 * @property {string} [Conversion]
 */


export class Share {
    /**
     * @param {ShareData} [data]
     */
    constructor(data = {}) {
        /** @type {string} */ this.ShareType = data.ShareType || "";
        /** @type {string} */ this.PathOrToken = data.PathOrToken || "";
        /** @type {string} */ this.PathMapped = data.PathMapped || "";
        /** @type {string} */ this.Owner = data.Owner || "";
        /** @type {string} */ this.User = data.User || "";
        /** @type {string} */ this.Permissions = data.Permissions || "r";
        /** @type {?boolean} */ this.EnabledByOwner = data.EnabledByOwner ?? null;
        /** @type {?boolean} */ this.EnabledByUser = data.EnabledByUser ?? null;
        /** @type {?boolean} */ this.HiddenByOwner = data.HiddenByOwner ?? null;
        /** @type {?boolean} */ this.HiddenByUser = data.HiddenByUser ?? null;
        /** @type {number} */ this.TimestampCreated = data.TimestampCreated || 0;
        /** @type {number} */ this.TimestampUpdated = data.TimestampUpdated || 0;
        /** @type {Object<String, String>} */ this.Properties = data.Properties || {};
        /** @type {string} */ this.Conversion = data.Conversion || "";
    }
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {?import("../models/collection.js").Collection} collection
 * @param {function(Array<Share>, ?string):void} callback
 */
export function reload_sharing_list(user, password, collection, callback) {
    let body = collection ? { PathMapped: decodeURIComponent(collection.href) } : {};
    return call_sharing_api(
        user,
        password,
        "all/list",
        body,
        function (response) {
            let parsed = JSON.parse(response);
            let shares = (parsed["Content"] || []).map((/** @type {ShareData} */ data) => new Share(data));
            callback(shares, null);
        },
        function () {
            // sharing is disabled on the server
            callback([], null);
        },
        function (error) {
            callback([], error);
        },
    );
}

/**
 * Property keys for different collection types.
 * Map to OVERLAY_PROPERTIES_WHITELIST in radicale/sharing/__init__.py
 */
export const OVERLAY_PROPERTIES = {
    CALENDAR: {
        DESCRIPTION: "C:calendar-description",
        COLOR: "ICAL:calendar-color",
    },
    ADDRESSBOOK: {
        DESCRIPTION: "CR:addressbook-description",
        COLOR: "INF:addressbook-color",
    }
};

/**
 * Returns the correct internal property key for a given collection type and property name.
 * @param {string} type Collection type (ADDRESSBOOK, CALENDAR, etc.)
 * @param {"DISPLAYNAME" | "DESCRIPTION" | "COLOR"} property Property name
 * @returns {string | null} Internal property key or null if not supported
 */
export function get_property_key(type, property) {
    if (property === "DISPLAYNAME") {
        return "D:displayname";
    }
    if (type === CollectionType.ADDRESSBOOK) {
        return OVERLAY_PROPERTIES.ADDRESSBOOK[property];
    } else if (CollectionType.is_subset(CollectionType.CALENDAR, type)) {
        return OVERLAY_PROPERTIES.CALENDAR[property];
    }
    return null;
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function add_share_by_token(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "token/create",
        {
            PathMapped: decodeURIComponent(share.PathMapped),
            Permissions: share.Permissions,
            Enabled: share.EnabledByOwner,
            Hidden: share.HiddenByOwner,
            Properties: share.Properties,
            Conversion: share.Conversion,
        },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function add_share_by_map(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "map/create",
        {
            PathMapped: decodeURIComponent(share.PathMapped),
            Permissions: share.Permissions,
            Enabled: share.EnabledByOwner,
            Hidden: share.HiddenByOwner,
            Properties: share.Properties,
            User: share.User,
            PathOrToken: decodeURIComponent(share.PathOrToken),
            Conversion: share.Conversion,
        },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function delete_share_by_token(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "token/delete",
        { PathOrToken: decodeURIComponent(share.PathOrToken) },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function delete_share_by_map(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "map/delete",
        { PathOrToken: decodeURIComponent(share.PathOrToken) },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}
/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function update_share_by_token(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "token/update",
        {
            PathOrToken: decodeURIComponent(share.PathOrToken),
            Permissions: share.Permissions,
            Enabled: share.EnabledByOwner,
            Hidden: share.HiddenByOwner,
            Properties: share.Properties,
            Conversion: share.Conversion,
        },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

/**
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function update_share_by_map(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        "map/update",
        {
            PathOrToken: decodeURIComponent(share.PathOrToken),
            PathMapped: decodeURIComponent(share.PathMapped),
            User: share.User,
            Permissions: share.Permissions,
            Enabled: share.EnabledByOwner,
            Hidden: share.HiddenByOwner,
            Properties: share.Properties,
            Conversion: share.Conversion,
        },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

/**
 * Update a shared map entry as the recipient user.
 * Only sends fields the non-owner user is allowed to change: PathOrToken, EnabledByUser, HiddenByUser.
 * @param {string} user
 * @param {?string} password
 * @param {Share} share
 * @param {function(?string):void} callback
 */
export function update_incoming_share(
    user,
    password,
    share,
    callback,
) {
    call_sharing_api(
        user,
        password,
        share.ShareType + "/update",
        {
            PathOrToken: decodeURIComponent(share.PathOrToken),
            Enabled: share.EnabledByUser,
            Hidden: share.HiddenByUser,
        },
        function (response) {
            let json_response = JSON.parse(response);
            if (json_response["Status"] !== "success") {
                callback(json_response["Status"] || "Unknown error");
            } else {
                callback(null);
            }
        },
        null,
        function (error) {
            callback(error);
        }
    );
}

