import MediumEditor from 'medium-editor';
import { ContentSerializer } from './ContentSerializer';
import { ChangeTracker } from './ChangeTracker';
import { HrefEditor } from './HrefEditor';
import { ApiClient } from '../../core/ApiClient';
import { StatusMessenger } from '../../core/StatusMessenger';
import { showToast } from '../../core/Toast';
import type { SaveContentRequest, SaveContentResponse } from '../../types/api/EditorApi';

/**
 * Extra save step run alongside the main content save (e.g. persisting the page's tag
 * assignment, which lives on a separate entity relation, not in the `content` JSON).
 * Returning `false` signals a failure so `saveContent()` can surface a single error status
 * without partially reporting success.
 */
export type AdditionalSaveHandler = () => Promise<boolean>;

export class EditorCore {
    private simpleEditor: MediumEditor.MediumEditor | null = null;
    private multilineEditor: MediumEditor.MediumEditor | null = null;
    private richEditor: MediumEditor.MediumEditor | null = null;
    private changeTracker: ChangeTracker;
    private serializer: ContentSerializer;
    private statusMessenger: StatusMessenger;
    private apiClient: ApiClient;
    private additionalSaveHandlers: AdditionalSaveHandler[] = [];
    private hrefEditor: HrefEditor | null = null;

    constructor() {
        this.serializer = new ContentSerializer();
        this.statusMessenger = new StatusMessenger('save-status');
        this.apiClient = new ApiClient();
        this.changeTracker = new ChangeTracker(
            this.saveContent.bind(this),
            this.statusMessenger
        );
    }

    public initialize(): void {
        this.initializeMediumEditor();
        this.attachEventListeners();
        this.hrefEditor = new HrefEditor(() => {
            this.changeTracker.markChanged();
        });
    }

    /**
     * Returns the shared ChangeTracker so other editor modules (BlockListEditor, TagPicker)
     * can mark the page dirty for non-field actions (add/remove/reorder block, tag toggle,
     * mobile-position toggle) using the same dirty-state mechanism as text-field edits.
     */
    public getChangeTracker(): ChangeTracker {
        return this.changeTracker;
    }

    /**
     * Registers an extra persistence step to run whenever the main "Enregistrer" action
     * fires, alongside the `content` JSON save. Used by TagPicker to persist the page's
     * tag assignment via its own endpoint (tags are an entity relation, not part of
     * `content`), so a single save action commits both without the admin needing two
     * separate buttons.
     */
    public registerAdditionalSaveHandler(handler: AdditionalSaveHandler): void {
        this.additionalSaveHandlers.push(handler);
    }

    private initializeMediumEditor(): void {
        const simpleEditables = document.querySelectorAll<HTMLElement>(
            '[data-editable="true"][data-editor-type="simple"]'
        );
        const multilineEditables = document.querySelectorAll<HTMLElement>(
            '[data-editable="true"][data-editor-type="simple-multiline"]'
        );
        const richEditables = document.querySelectorAll<HTMLElement>(
            '[data-editable="true"]:not([data-editor-type="simple"]):not([data-editor-type="simple-multiline"])'
        );

        if (simpleEditables.length > 0) {
            this.simpleEditor = new MediumEditor(simpleEditables, {
                toolbar: { buttons: ['bold', 'italic'] },
                placeholder: { text: 'Cliquez pour éditer...' },
                disableReturn: true,
                disableExtraSpaces: true
            });
        }

        if (multilineEditables.length > 0) {
            this.multilineEditor = this.createMultilineEditor(multilineEditables);
        }

        if (richEditables.length > 0) {
            this.richEditor = new MediumEditor(richEditables, {
                toolbar: { buttons: ['bold', 'italic', 'underline', 'anchor'] },
                placeholder: { text: 'Cliquez pour éditer...' }
            });
        }
    }

    /**
     * "simple-multiline" fields (e.g. a postal address) keep the bold/italic-only toolbar
     * of "simple" fields but allow Enter to insert line breaks. MediumEditor's default Enter
     * handling wraps each line in a `<p>`, which reads oddly for a short address block, so
     * Enter is remapped to insert `<br>` instead (Shift+Enter already does this natively).
     */
    private createMultilineEditor(elements: HTMLElement[] | NodeListOf<HTMLElement>): MediumEditor.MediumEditor {
        const editor = new MediumEditor(elements, {
            toolbar: { buttons: ['bold', 'italic'] },
            placeholder: { text: 'Cliquez pour éditer...' },
            disableDoubleReturn: true,
            disableExtraSpaces: true
        });

        editor.subscribe('editableKeydownEnter', (event: KeyboardEvent) => {
            event.preventDefault();
            document.execCommand('insertHTML', false, '<br>');
        });

        return editor;
    }

    private attachEventListeners(): void {
        const editables = document.querySelectorAll<HTMLElement>('[data-editable="true"]');
        editables.forEach(el => this.attachInputListener(el));

        const editableSelects = document.querySelectorAll<HTMLSelectElement>('[data-editable-select="true"]');
        editableSelects.forEach(el => {
            el.addEventListener('change', () => {
                this.changeTracker.markChanged();
            });
        });

        document.getElementById('save-btn')?.addEventListener('click', (e) => {
            e.preventDefault();
            this.saveContent();
        });

        document.getElementById('publish-toggle-btn')?.addEventListener('click', (e) => {
            e.preventDefault();
            this.togglePublish();
        });
    }

    private async togglePublish(): Promise<void> {
        const btn = document.getElementById('publish-toggle-btn') as HTMLButtonElement | null;
        if (!btn) return;

        const pageId = btn.dataset.pageId;
        if (!pageId) return;

        btn.disabled = true;

        try {
            const response = await this.apiClient.post<{ isPublished: boolean }>(
                `/api/admin/pages/${pageId}/toggle-publish`,
                {}
            );

            if (response.success && response.data) {
                this.updatePublishButton(btn, response.data.isPublished);
            } else {
                showToast(response.error || 'Erreur lors de la modification', 'error');
            }
        } catch (error) {
            showToast('Impossible de contacter le serveur. Vérifiez votre connexion.', 'error');
            console.error('[EditorCore] Publish toggle error:', error);
        } finally {
            btn.disabled = false;
        }
    }

    private updatePublishButton(btn: HTMLButtonElement, isPublished: boolean): void {
        btn.dataset.published = isPublished ? '1' : '0';
        const label = isPublished ? 'Dépublier' : 'Publier';
        btn.setAttribute('data-tooltip', label);
        btn.setAttribute('aria-label', label);

        const icon = btn.querySelector('.icon');
        if (icon) {
            icon.className = `icon icon--${isPublished ? 'eye-off' : 'eye'} editor-toolbar__action-icon`;
            (icon as HTMLElement).style.setProperty(
                '--icon-url',
                `url('/build/images/icons/${isPublished ? 'eye-off' : 'eye'}.svg')`
            );
        }
    }

    private attachInputListener(el: HTMLElement): void {
        el.addEventListener('input', () => {
            this.changeTracker.markChanged();
        });
    }

    /**
     * Binds MediumEditor + dirty-tracking to one or more editable elements that were
     * inserted into the DOM *after* bootstrap (e.g. a dynamically added Actualité block).
     *
     * Dispatches each element to the same editor instance ("simple" vs "rich") it would
     * have joined at bootstrap time, based on `data-editor-type`, using MediumEditor's
     * `addElements()` API rather than creating a second, disconnected editor instance
     * (which would produce a different toolbar/behavior than server-rendered fields).
     *
     * If no editor instance of the required kind exists yet (e.g. a page with zero
     * server-rendered simple-editables at load time, so `simpleEditor` was never created),
     * a new instance is lazily created for that kind, mirroring `initializeMediumEditor()`'s
     * options exactly.
     */
    public bindEditableElement(elements: HTMLElement | HTMLElement[]): void {
        const elementList = Array.isArray(elements) ? elements : [elements];

        const simpleElements = elementList.filter(
            el => el.dataset.editorType === 'simple'
        );
        const multilineElements = elementList.filter(
            el => el.dataset.editorType === 'simple-multiline'
        );
        const richElements = elementList.filter(
            el => el.dataset.editorType !== 'simple' && el.dataset.editorType !== 'simple-multiline'
        );

        if (simpleElements.length > 0) {
            if (this.simpleEditor) {
                this.simpleEditor.addElements(simpleElements);
            } else {
                this.simpleEditor = new MediumEditor(simpleElements, {
                    toolbar: { buttons: ['bold', 'italic'] },
                    placeholder: { text: 'Cliquez pour éditer...' },
                    disableReturn: true,
                    disableExtraSpaces: true
                });
            }
        }

        if (multilineElements.length > 0) {
            if (this.multilineEditor) {
                this.multilineEditor.addElements(multilineElements);
            } else {
                this.multilineEditor = this.createMultilineEditor(multilineElements);
            }
        }

        if (richElements.length > 0) {
            if (this.richEditor) {
                this.richEditor.addElements(richElements);
            } else {
                this.richEditor = new MediumEditor(richElements, {
                    toolbar: { buttons: ['bold', 'italic', 'underline', 'anchor'] },
                    placeholder: { text: 'Cliquez pour éditer...' }
                });
            }
        }

        elementList.forEach(el => this.attachInputListener(el));
    }

    /**
     * Wires the href-edit badge + click-prevent onto `[data-editable-href="true"]` links
     * inserted into the DOM after bootstrap (e.g. a dynamically added Actualité block).
     * Without this, a newly added block's link keeps its real `href` and navigates on
     * click instead of opening the href-edit modal (see bindEditableElement doc above
     * for the equivalent gap this closes for MediumEditor text fields).
     */
    public bindHrefElement(elements: HTMLAnchorElement | HTMLAnchorElement[]): void {
        const elementList = Array.isArray(elements) ? elements : [elements];
        if (elementList.length === 0) return;

        if (!this.hrefEditor) {
            this.hrefEditor = new HrefEditor(() => {
                this.changeTracker.markChanged();
            });
        }
        this.hrefEditor.bindLinks(elementList);
    }

    private async saveContent(): Promise<void> {
        if (!this.changeTracker.hasChanges()) return;

        this.statusMessenger.show('Sauvegarde en cours...', 'info');

        const content = this.serializer.serialize();
        const entityType = document.body.dataset.entityType!;
        const entityId = document.body.dataset.entityId!;
        const locale = (document.body.dataset.editLocale || 'fr') as 'fr' | 'en';
        const title = this.serializer.extractTitle();

        const request: SaveContentRequest = { content, locale };
        if (title !== null) {
            request.title = title;
        }

        try {
            const response = await this.apiClient.post<SaveContentResponse>(
                `/api/admin/editor/save/${entityType}/${entityId}`,
                request
            );

            const additionalResults = await Promise.all(
                this.additionalSaveHandlers.map(handler => handler())
            );
            const additionalSuccess = additionalResults.every(result => result);

            if (response.success && additionalSuccess) {
                this.changeTracker.markSaved();
                this.statusMessenger.show('Sauvegardé', 'success');
                setTimeout(() => this.statusMessenger.hide(), 3000);
            } else {
                const message = response.error || 'Une erreur est survenue lors de la sauvegarde.';
                this.statusMessenger.show('Erreur de sauvegarde', 'error');
                showToast(message, 'error');
            }
        } catch (error) {
            this.statusMessenger.show('Erreur de sauvegarde', 'error');
            showToast('Impossible de contacter le serveur. Vérifiez votre connexion.', 'error');
            console.error('[EditorCore] Save error:', error);
        }
    }

    public destroy(): void {
        this.simpleEditor?.destroy();
        this.multilineEditor?.destroy();
        this.richEditor?.destroy();
        this.changeTracker.destroy();
    }
}
