import { ConnectionOpinionModel } from "../models/ConnectionOpinionModel.js";
import { ConnectionOpinionResolver } from "./ConnectionOpinionResolver.js";
import { ConnectionStore } from "./ConnectionStore.js";

export class ConnectionOpinionService {
    static canTrack(edgeOrId) {
        const edge = this.#getEdge(edgeOrId);
        if (!edge) return false;
        return ConnectionOpinionModel.canTrack(
            ConnectionStore.getNode(edge.sourceNodeId),
            ConnectionStore.getNode(edge.targetNodeId)
        );
    }

    static isTracked(edgeOrId) {
        return ConnectionOpinionModel.isTracked(this.#getEdge(edgeOrId));
    }

    static resolve(edgeOrId) {
        return ConnectionOpinionResolver.resolve(edgeOrId);
    }

    static resolvePerspective(edgeOrId, observerNodeId) {
        return ConnectionOpinionResolver.resolvePerspective(edgeOrId, observerNodeId);
    }

    static async enable(edgeId) {
        const edge = this.#getEdge(edgeId);
        if (!edge || !this.canTrack(edge)) return null;
        if (this.isTracked(edge)) return edge;
        return ConnectionStore.updateConnection(edge.id, { opinions: ConnectionOpinionModel.create() });
    }

    static async disable(edgeId) {
        const edge = this.#getEdge(edgeId);
        if (!edge) return null;
        return ConnectionStore.updateConnection(edge.id, { opinions: null });
    }

    static async setBase(edgeId, observerNodeId, value) {
        return this.#updatePerspective(edgeId, observerNodeId, perspective => ({
            ...perspective,
            base: ConnectionOpinionModel.normalizeValue(value)
        }));
    }

    static async addAdjustment(edgeId, observerNodeId, adjustment = {}, { bidirectional = false } = {}) {
        const now = Date.now();
        const normalized = ConnectionOpinionModel.normalizeAdjustment({
            ...adjustment,
            id: adjustment.id || foundry.utils.randomID(),
            source: adjustment.source || { kind: "manual", id: null },
            createdTime: adjustment.createdTime || now,
            updatedTime: now
        });
        if (!normalized) throw new Error("Opinion adjustments require a non-zero value and a reason.");
        if (bidirectional) {
            const edge = this.#getEdge(edgeId);
            if (!edge || !ConnectionOpinionModel.isTracked(edge)) return null;
            const subjectNodeId = ConnectionOpinionModel.getSubjectNodeId(edge, observerNodeId);
            if (!subjectNodeId) return null;

            const opinions = ConnectionOpinionModel.normalize(edge.opinions, edge) || ConnectionOpinionModel.create();
            const byObserver = { ...(opinions.byObserver || {}) };
            for (const nodeId of [observerNodeId, subjectNodeId]) {
                const perspective = ConnectionOpinionModel.getPerspective(opinions, nodeId);
                const next = ConnectionOpinionModel.normalizePerspective({
                    ...perspective,
                    adjustments: [...perspective.adjustments.filter(entry => entry.id !== normalized.id), normalized]
                });
                if (next) byObserver[nodeId] = next;
                else delete byObserver[nodeId];
            }
            await ConnectionStore.updateConnection(edge.id, {
                opinions: {
                    schemaVersion: ConnectionOpinionModel.SCHEMA_VERSION,
                    byObserver
                }
            });
            return normalized;
        }
        await this.#updatePerspective(edgeId, observerNodeId, perspective => ({
            ...perspective,
            adjustments: [...perspective.adjustments.filter(entry => entry.id !== normalized.id), normalized]
        }));
        return normalized;
    }

    static async updateAdjustment(edgeId, observerNodeId, adjustmentId, patch = {}) {
        let updated = null;
        await this.#updatePerspective(edgeId, observerNodeId, perspective => ({
            ...perspective,
            adjustments: perspective.adjustments.map(adjustment => {
                if (adjustment.id !== adjustmentId) return adjustment;
                updated = ConnectionOpinionModel.normalizeAdjustment({
                    ...adjustment,
                    ...patch,
                    id: adjustment.id,
                    source: patch.source || adjustment.source,
                    createdTime: adjustment.createdTime,
                    updatedTime: Date.now()
                });
                return updated || adjustment;
            })
        }));
        return updated;
    }

    static async removeAdjustment(edgeId, observerNodeId, adjustmentId) {
        const edge = await this.#updatePerspective(edgeId, observerNodeId, perspective => ({
            ...perspective,
            adjustments: perspective.adjustments.filter(adjustment => adjustment.id !== adjustmentId)
        }));
        return !!edge;
    }

    static async #updatePerspective(edgeId, observerNodeId, mutate) {
        const edge = this.#getEdge(edgeId);
        if (!edge || !ConnectionOpinionModel.isTracked(edge)) return null;
        if (![edge.sourceNodeId, edge.targetNodeId].includes(observerNodeId)) return null;

        const opinions = ConnectionOpinionModel.normalize(edge.opinions, edge) || ConnectionOpinionModel.create();
        const current = ConnectionOpinionModel.getPerspective(opinions, observerNodeId);
        const next = ConnectionOpinionModel.normalizePerspective(mutate(current));
        const byObserver = { ...(opinions.byObserver || {}) };
        if (next) byObserver[observerNodeId] = next;
        else delete byObserver[observerNodeId];
        return ConnectionStore.updateConnection(edge.id, {
            opinions: {
                schemaVersion: ConnectionOpinionModel.SCHEMA_VERSION,
                byObserver
            }
        });
    }

    static #getEdge(edgeOrId) {
        return typeof edgeOrId === "string" ? ConnectionStore.getConnectionEdge(edgeOrId) : edgeOrId || null;
    }
}
