/**
 * useEditor
 * Centralizes editor state for loaded models, annotation values, upload flows,
 * save actions, and 3D viewport settings used across the annotation workspace.
 */
import { useState, useRef, useEffect, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { calculateHash, hasObjectChanges, hasActualContent } from '../utils/editorUtils';
import { buildModelUploadGroups, validateModelFileBinding } from '../utils/modelUploadValidation';
import { getAnnotationDetail, saveAnnotation } from '../api/generated';
import { useLanguage } from '../context/LanguageContext';
import { useMessageDialog } from './useMessageDialog';
import { buildApiUrl, toAbsoluteApiUrl } from '../utils/api';
import { TRAIT_RELATIONS } from '../constants/traitRelationships';

const toAbsoluteUrl = (path) => {
    return toAbsoluteApiUrl(path);
};

const getMirroredTraitId = (traitId) => {
    const [namespace, rawId] = traitId.includes('::')
        ? traitId.split('::', 2)
        : [null, traitId];

    const mirroredRawId = TRAIT_RELATIONS[rawId];
    if (!mirroredRawId) return undefined;

    return namespace ? `${namespace}::${mirroredRawId}` : mirroredRawId;
};

const normalizeAnnotator = (value) => String(value || '').trim().toLowerCase();

export function useEditor({ defaultAnnotatorName = '' } = {}) {
    const { t } = useLanguage();
    const { alert: showAlert, confirm } = useMessageDialog();

    const [loadError, setLoadError] = useState(null);
    const [modelUrl, setModelUrl] = useState(null);
    const [modelName, setModelName] = useState('');
    const [fileHash, setFileHash] = useState(null);
    const [formValues, setFormValues] = useState({});
    const [annotatorName, setAnnotatorName] = useState(defaultAnnotatorName || '');
    const [mtlUrl, setMtlUrl] = useState(null);
    const [textureUrl, setTextureUrl] = useState(null);
    const [annotationId, setAnnotationId] = useState(null);
    const [atlasId, setAtlasId] = useState(null);
    const [loadedAnnotatorName, setLoadedAnnotatorName] = useState('');

    const currentFileRef = useRef(null);
    const currentMtlRef = useRef(null);
    const currentImgRef = useRef(null);
    const controlsRef = useRef();
    const lastSavedValues = useRef({});
    const previousDefaultAnnotatorRef = useRef(defaultAnnotatorName);

    const [isHighlighting, setIsHighlighting] = useState(false);
    const [isSaving, setIsSaving] = useState(false);
    const [lightIntensity, setLightIntensity] = useState([2]);
    const [lightAngle, setLightAngle] = useState([0]);
    const [isAutoRotate, setIsAutoRotate] = useState(false);

    const [visualSettings, setVisualSettings] = useState({
        smoothAngle: 0,
        wireframe: false,
        pointsMode: false,
        opacity: 1,
        showAxes: false,
        roughness: 0.8,
        metalness: 0.1,
        color: "#bbbbbb",
        showTexture: true,
    });

    const queryClient = useQueryClient();
    const hasChanges = hasObjectChanges(formValues, lastSavedValues.current);
    const currentHasContent = hasActualContent(formValues);
    const previousHadContent = hasActualContent(lastSavedValues.current);
    const isDirty = hasChanges && (currentHasContent || previousHadContent);

    useEffect(() => {
        const previousDefaultAnnotator = previousDefaultAnnotatorRef.current;
        if (!annotationId && (annotatorName === "" || annotatorName === previousDefaultAnnotator)) {
            setAnnotatorName(defaultAnnotatorName || '');
        }
        previousDefaultAnnotatorRef.current = defaultAnnotatorName;
    }, [annotationId, annotatorName, defaultAnnotatorName]);

    const isForeignAnnotation = Boolean(annotationId) &&
        Boolean(defaultAnnotatorName.trim()) &&
        Boolean(loadedAnnotatorName.trim()) &&
        normalizeAnnotator(loadedAnnotatorName) !== normalizeAnnotator(defaultAnnotatorName);

    const ensureWritableAnnotation = useCallback(() => {
        if (!isForeignAnnotation) {
            return false;
        }

        const nextAnnotator = defaultAnnotatorName.trim();
        const pureModelName = modelName.includes(' (')
            ? modelName.split(' (')[0]
            : modelName.replace(/\.[^/.]+$/, '');

        setAnnotationId(null);
        setAnnotatorName(nextAnnotator);
        setModelName(`${pureModelName} (${nextAnnotator || t('valueNotSpecified')})`);

        return true;
    }, [defaultAnnotatorName, isForeignAnnotation, modelName, t]);

    const handleInputChange = useCallback((id, value) => {
        ensureWritableAnnotation();
        setFormValues(prev => {
            const next = { ...prev };
            const isRemoving = next[id] === value;

            // 1. Toggle logic: if clicking the same value, remove it; else set it.
            if (isRemoving) {
                delete next[id];
            } else {
                next[id] = value;
            }

            // 2. Bilateral mirroring logic: mirror only when setting a value,
            // and only if the mirrored trait is currently empty.
            const mirroredId = getMirroredTraitId(id);
            if (mirroredId && !next[mirroredId] && !isRemoving && value !== undefined && value !== null) {
                next[mirroredId] = value;
            }

            return next;
        });
    }, [ensureWritableAnnotation]);

    const handleResetForm = useCallback(async (silent = false) => {
        const shouldReset = silent === true
            ? true
            : await confirm({
                title: t('dialogConfirmTitle'),
                message: t('confirmResetForm'),
                tone: 'warning',
                confirmLabel: t('dialogClearFormAction'),
            });

        if (shouldReset) {
            ensureWritableAnnotation();
            setFormValues({});
            if (silent === true) lastSavedValues.current = {};
        }
        return shouldReset;
    }, [confirm, ensureWritableAnnotation, t]);

    const rotateModel = useCallback((axis, angle) => {
        if (!controlsRef.current) return;
        if (axis === 'y') controlsRef.current.setAzimuthalAngle(controlsRef.current.getAzimuthalAngle() + angle);
        if (axis === 'x') controlsRef.current.setPolarAngle(controlsRef.current.getPolarAngle() + angle);
        controlsRef.current.update();
    }, []);

    const loadModelFromHistory = useCallback(async (id) => {
        if (!id || String(id).includes('{')) return;
        setLoadError(null);
        try {
            const { data, error } = await getAnnotationDetail({
                path: { annotationId: id }
            });

            if (error || !data) throw new Error(t('errorDataLoading'));
            const payload = data.data || data;

            setAtlasId(payload.atlasId);
            setAnnotationId(id);
            setFileHash(payload.fileHash || payload.file_hash);
            setLoadedAnnotatorName(payload.annotator || '');

            const pureId = (payload.modelLabel || payload.modelId || '').replace(/\.[^/.]+$/, '');
            const displayAnnotator = payload.annotator || t('valueNotSpecified');
            setModelName(`${pureId} (${displayAnnotator})`);

            setAnnotatorName(payload.annotator || '');
            setFormValues(payload.values || {});
            lastSavedValues.current = payload.values || {};

            setModelUrl(toAbsoluteUrl(payload.modelPath || payload.model_path));
            setMtlUrl(toAbsoluteUrl(payload.mtlPath || payload.mtl_path));
            setTextureUrl(toAbsoluteUrl(payload.texturePath || payload.texture_path));

            currentFileRef.current = null;
            currentMtlRef.current = null;
            currentImgRef.current = null;
        } catch (err) {
            console.error('Annotation detail load error:', err);
            setLoadError(err.message);
        }
    }, [t]);

    const loadModelFromModel = useCallback(async (model) => {
        setLoadError(null);
        try {
            setAnnotationId(null);
            setFileHash(model.file_hash);
            setLoadedAnnotatorName(defaultAnnotatorName || '');

            setModelName(model.external_id);
            setAnnotatorName(defaultAnnotatorName || '');
            setFormValues({});
            lastSavedValues.current = {};

            setModelUrl(toAbsoluteUrl(model.model_path));
            setMtlUrl(toAbsoluteUrl(model.mtl_path));
            setTextureUrl(toAbsoluteUrl(model.texture_path));

            currentFileRef.current = null;
            currentMtlRef.current = null;
            currentImgRef.current = null;
        } catch (err) {
            console.error('Model load error:', err);
            setLoadError(err.message);
        }
    }, [defaultAnnotatorName]);

    const handleSaveToDB = useCallback(async () => {
        const cleanName = annotatorName.trim();
        if (!cleanName) {
            await showAlert({
                title: t('dialogWarningTitle'),
                message: t('errorEnterAnnotator'),
                tone: 'warning',
            });
            return;
        }

        setIsSaving(true);
        try {
            const pureIdForDB = modelName.includes(' (')
                ? modelName.split(' (')[0]
                : modelName.replace(/\.[^/.]+$/, '');

            const { data, error } = await saveAnnotation({
                body: {
                    annotationId: annotationId || undefined,
                    atlasId: String(atlasId),
                    modelId: pureIdForDB,
                    annotatorName: cleanName,
                    fileHash: fileHash,
                    values: JSON.stringify(formValues),
                    modelFile: currentFileRef.current || undefined,
                    mtlFile: currentMtlRef.current || undefined,
                    textureFile: currentImgRef.current || undefined,
                }
            });

            if (error) {
                const errorMsg = error.error || error.message || 'saveError';
                throw new Error(t(errorMsg));
            }

            if (data && data.annotationId) {
                setAnnotationId(data.annotationId);
                setLoadedAnnotatorName(cleanName);
                lastSavedValues.current = { ...formValues };

                setModelName(`${pureIdForDB} (${cleanName})`);

                await queryClient.invalidateQueries({ queryKey: ['annotations'] });

                currentFileRef.current = null;
                currentMtlRef.current = null;
                currentImgRef.current = null;
            }
        } catch (e) {
            console.error('Save error:', e);
            await showAlert({
                title: t('dialogErrorTitle'),
                message: e.message,
                tone: 'error',
            });
        } finally {
            setIsSaving(false);
        }
    }, [annotationId, annotatorName, atlasId, fileHash, formValues, modelName, queryClient, showAlert, t]);

    const handleSelectedFiles = useCallback(async (files) => {
        if (files.length === 0) return;

        const [group] = await buildModelUploadGroups(files);
        const objFile = group?.obj || null;
        const mtlFile = group?.mtl || null;
        const imgFile = group?.texture || null;

        if (!objFile) {
            await showAlert({
                title: t('dialogWarningTitle'),
                message: t('missingObjFile'),
                tone: 'warning',
            });
            event.target.value = '';
            return;
        }

        const finalMtl = mtlFile || null;
        const finalImg = imgFile || null;

        const relationCheck = await validateModelFileBinding({
            objFile,
            mtlFile: finalMtl,
            textureFile: finalImg
        });
        if (!relationCheck.isValid) {
            await showAlert({
                title: t('dialogWarningTitle'),
                message: t('filesNotCorresponding'),
                details: t(relationCheck.messageKey),
                tone: 'warning',
            });
            event.target.value = '';
            return;
        }

        try {
            const computedHash = await calculateHash(objFile);
            const defaultSignature = defaultAnnotatorName.trim();

            const pureName = objFile.name.replace(/\.[^/.]+$/, "");
            const displayAnnotator = defaultSignature || t('valueNotSpecified');

            const modelsRes = await fetch(buildApiUrl('/api/models'));
            const allModels = modelsRes.ok ? await modelsRes.json() : [];
            const existingByHash = allModels.find(m => m.file_hash === computedHash);
            if (existingByHash) {
                await showAlert({
                    title: t('modelExistsTitle'),
                    message: t('modelAlreadyUploadedAs').replace('{name}', existingByHash.external_id || t('unknownAnnotator')),
                    tone: 'warning',
                });
                return;
            }

            const existingByName = allModels.find(m => String(m.external_id || '').toLowerCase() === pureName.toLowerCase());
            if (existingByName) {
                await showAlert({
                    title: t('modelExistsTitle'),
                    message: t('modelWithSameNameExists'),
                    details: existingByName.external_id,
                    tone: 'warning',
                });
                return;
            }

            setAnnotationId(null);
            setFileHash(computedHash);
            setLoadedAnnotatorName(defaultSignature);
            setAnnotatorName(defaultSignature);
            setModelName(`${pureName} (${displayAnnotator})`);
            setFormValues({});
            lastSavedValues.current = {};

            setModelUrl(URL.createObjectURL(objFile));
            currentFileRef.current = objFile;

            setMtlUrl(URL.createObjectURL(finalMtl));
            setTextureUrl(URL.createObjectURL(finalImg));
            currentMtlRef.current = finalMtl;
            currentImgRef.current = finalImg;

        } catch (error) {
            console.error('File upload error:', error);
            await showAlert({
                title: t('dialogErrorTitle'),
                message: t('errorLoading'),
                tone: 'error',
            });
        }
    }, [defaultAnnotatorName, showAlert, t]);

    const handleFileUpload = useCallback(async (event) => {
        const files = Array.from(event.target.files || []);
        try {
            await handleSelectedFiles(files);
        } finally {
            event.target.value = '';
        }
    }, [handleSelectedFiles]);

    return {
        state: {
            loadError, modelUrl, modelName, fileHash, formValues,
            isSaving, isDirty, annotationId, annotatorName,
            mtlUrl, textureUrl, isHighlighting,
            lightIntensity, lightAngle, isAutoRotate, visualSettings, atlasId,
            isForeignAnnotation,
            currentAnnotatorName: defaultAnnotatorName,
        },
        refs: { controlsRef },
        actions: {
            setLoadError, handleFileUpload, handleSelectedFiles, handleSaveToDB, loadModelFromHistory, loadModelFromModel,
            handleInputChange, handleResetForm, rotateModel,
            resetView: () => controlsRef.current?.reset(),
            setLightIntensity, setLightAngle, setIsAutoRotate,
            ensureWritableAnnotation,
            setAnnotatorName, setVisualSettings, setIsHighlighting,
            setModelUrl, setMtlUrl, setTextureUrl, setFormValues, setAtlasId, setAnnotationId
        }
    };
}