/**
 * Test-Driven Section Editor Implementation
 *
 * A clean, object-oriented approach to handling section editing
 * that can be tested independently of the DOM.
 */

// Enums for clear state management
const EditState = Object.freeze({
    ORIGINAL: 'original',
    EDITING: 'editing',
    MODIFIED: 'modified',
    SAVED: 'saved'
});

const SectionType = Object.freeze({
    HEADING: 'heading',
    PARAGRAPH: 'paragraph',
    LIST: 'list',
    CODE: 'code',
    BLOCKQUOTE: 'blockquote'
});

/**
 * Section class - Core business logic for a single editable section
 *
 * Responsibilities:
 * - Track original and current content
 * - Manage edit state transitions
 * - Validate content changes
 * - Generate stable identifiers
 */
class Section {
    constructor(id, originalMarkdown, sectionType = SectionType.PARAGRAPH) {
        this.id = id;
        this.originalMarkdown = originalMarkdown; // Never changes - always original from render
        this.currentMarkdown = originalMarkdown;  // Current "saved" state
        this.editingMarkdown = null; // Content being edited (only during editing)
        this.pendingMarkdown = null; // Unsaved pending changes (when not actively editing)
        this.sectionType = sectionType;
        this.state = EditState.ORIGINAL;
        this.domElement = null; // Will be set by DOM renderer
        this.lastSaved = null;
        this.created = new Date();
    }

    /**
     * Start editing this section
     * @returns {string} The markdown content to populate the editor with
     */
    startEdit() {
        if (this.state === EditState.EDITING) {
            throw new Error(`Section ${this.id} is already being edited`);
        }

        // Use pending changes if they exist, otherwise use current content
        this.editingMarkdown = this.pendingMarkdown || this.currentMarkdown;
        this.state = EditState.EDITING;
        return this.editingMarkdown;
    }

    /**
     * Update the content during editing
     * @param {string} markdown - The new markdown content
     */
    updateContent(markdown) {
        if (this.state !== EditState.EDITING) {
            throw new Error(`Section ${this.id} is not in editing state`);
        }

        this.editingMarkdown = markdown;
        // State remains EDITING; we don't change it until an action is taken
    }

    /**
     * Accept the current changes and save them
     * @returns {string} The saved markdown content
     */
    acceptChanges() {
        if (this.state !== EditState.EDITING) {
            throw new Error(`Section ${this.id} is not in editing state`);
        }

        // Make the edited content the new current content
        this.currentMarkdown = this.editingMarkdown;
        this.editingMarkdown = null;
        this.pendingMarkdown = null; // Clear any pending changes
        // Set to SAVED to indicate this section has been explicitly saved
        this.state = EditState.SAVED;
        this.lastSaved = new Date();
        return this.currentMarkdown;
    }

    /**
     * Cancel editing and revert to state before editing started
     * @returns {string} The content that was current before editing
     */
    cancelChanges() {
        if (this.state !== EditState.EDITING) {
            throw new Error(`Section ${this.id} is not in editing state`);
        }

        // Discard editing content and return to the state before editing started
        this.editingMarkdown = null;
        // Keep any existing pending changes from before this edit session
        // Return to the appropriate state
        if (this.pendingMarkdown !== null) {
            this.state = EditState.MODIFIED; // Has pending changes
            return this.pendingMarkdown;
        } else if (this.lastSaved !== null) {
            this.state = EditState.SAVED;
            return this.currentMarkdown;
        } else {
            this.state = this.hasChanges() ? EditState.MODIFIED : EditState.ORIGINAL;
            return this.currentMarkdown;
        }
    }

    /**
     * Reset content to original state from render time
     * Can be called in any state; returns section to completely original state
     * @returns {string} The original markdown content
     */
    resetToOriginal() {
        // Reset everything to original state from render time
        this.currentMarkdown = this.originalMarkdown;
        this.editingMarkdown = null;
        this.pendingMarkdown = null; // Clear pending changes
        this.lastSaved = null; // Clear save timestamp
        this.state = EditState.ORIGINAL;
        return this.originalMarkdown;
    }

    /**
     * Stop editing without saving (preserves pending changes as modified)
     * @returns {EditState} The new state
     */
    stopEditing() {
        if (this.state !== EditState.EDITING) {
            return this.state;
        }

        // If we have editing changes that differ from current content, preserve them as pending
        if (this.editingMarkdown && this.editingMarkdown !== this.currentMarkdown) {
            this.pendingMarkdown = this.editingMarkdown;
            this.state = EditState.MODIFIED; // Has pending changes
        } else {
            // No changes made during this edit session
            this.pendingMarkdown = null;
            if (this.lastSaved !== null) {
                this.state = EditState.SAVED;
            } else {
                this.state = this.hasChanges() ? EditState.MODIFIED : EditState.ORIGINAL;
            }
        }

        this.editingMarkdown = null;
        return this.state;
    }

    /**
     * Check if the section has unsaved changes
     * @returns {boolean}
     */
    hasChanges() {
        return this.currentMarkdown !== this.originalMarkdown;
    }

    /**
     * Check if the section is currently being edited
     * @returns {boolean}
     */
    isEditing() {
        return this.state === EditState.EDITING;
    }

    /**
     * Check if the section has been modified but not saved
     * @returns {boolean}
     */
    isModified() {
        return this.state === EditState.MODIFIED;
    }

    /**
     * Get a summary of the section's current state
     * @returns {Object}
     */
    getStatus() {
        return {
            id: this.id,
            state: this.state,
            hasChanges: this.hasChanges(),
            isEditing: this.isEditing(),
            isModified: this.isModified(),
            contentLength: this.currentMarkdown.length,
            lastSaved: this.lastSaved,
            sectionType: this.sectionType
        };
    }

    /**
     * Generate a stable ID from content and position
     * @param {string} content - The section content
     * @param {number} position - The section position
     * @returns {string} A stable section ID
     */
    static generateId(content, position) {
        // Create a simple hash from content + position
        const str = content.substring(0, 100) + position.toString();
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32-bit integer
        }
        return `section_${Math.abs(hash)}_${position}`;
    }

    /**
     * Determine section type from markdown content
     * @param {string} markdown - The markdown content
     * @returns {SectionType}
     */
    static detectType(markdown) {
        const trimmed = markdown.trim();

        if (trimmed.startsWith('#')) return SectionType.HEADING;
        if (trimmed.startsWith('```')) return SectionType.CODE;
        if (trimmed.startsWith('>')) return SectionType.BLOCKQUOTE;
        if (trimmed.startsWith('-') || trimmed.startsWith('*') || /^\d+\./.test(trimmed)) {
            return SectionType.LIST;
        }

        return SectionType.PARAGRAPH;
    }
}

/**
 * SectionManager class - Manages the collection of sections
 *
 * Responsibilities:
 * - Track all sections in the document
 * - Handle section lifecycle (create, edit, save, delete)
 * - Manage edit state transitions between sections
 * - Provide document-level operations
 */
class SectionManager {
    constructor() {
        this.sections = new Map(); // id -> Section
        this.editingSection = null; // Currently editing section ID
        this.listeners = new Map(); // event -> [callbacks]
    }

    /**
     * Add event listener
     * @param {string} event - Event name
     * @param {Function} callback - Callback function
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }

    /**
     * Emit event to listeners
     * @param {string} event - Event name
     * @param {*} data - Event data
     */
    emit(event, data) {
        if (this.listeners.has(event)) {
            this.listeners.get(event).forEach(callback => callback(data));
        }
    }

    /**
     * Create sections from markdown content
     * @param {string} markdownContent - The full markdown document
     * @returns {Array<Section>} Created sections
     */
    createSectionsFromMarkdown(markdownContent) {
        const lines = markdownContent.split('\n');
        const sections = [];
        let currentSection = '';
        let position = 0;

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];

            // Check if this line starts a new section
            const isHeading = /^#{1,6}\s/.test(line);
            const isNewParagraph = line.trim() && i > 0 && !lines[i-1].trim();
            const isNewSection = isHeading || isNewParagraph;

            if (isNewSection && currentSection.trim()) {
                // Complete the previous section
                const sectionId = Section.generateId(currentSection, position);
                const sectionType = Section.detectType(currentSection);
                const section = new Section(sectionId, currentSection.trim(), sectionType);

                sections.push(section);
                this.sections.set(sectionId, section);
                position++;

                currentSection = line;
            } else {
                if (currentSection) currentSection += '\n';
                currentSection += line;
            }
        }

        // Add the last section
        if (currentSection.trim()) {
            const sectionId = Section.generateId(currentSection, position);
            const sectionType = Section.detectType(currentSection);
            const section = new Section(sectionId, currentSection.trim(), sectionType);

            sections.push(section);
            this.sections.set(sectionId, section);
        }

        this.emit('sections-created', { sections, count: sections.length });
        return sections;
    }

    /**
     * Start editing a section
     * @param {string} sectionId - The section ID to edit
     * @returns {string} The content to populate the editor with
     */
    startEditing(sectionId) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        // Stop editing any other section first
        if (this.editingSection && this.editingSection !== sectionId) {
            this.stopEditing(this.editingSection);
        }

        const content = section.startEdit();
        this.editingSection = sectionId;

        this.emit('edit-started', { sectionId, content, section: section.getStatus() });
        return content;
    }

    /**
     * Update content for the currently editing section
     * @param {string} sectionId - The section ID
     * @param {string} markdown - The new content
     */
    updateContent(sectionId, markdown) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        section.updateContent(markdown);
        this.emit('content-updated', { sectionId, markdown, section: section.getStatus() });
    }

    /**
     * Accept changes for a section
     * @param {string} sectionId - The section ID
     * @returns {string} The saved content
     */
    acceptChanges(sectionId) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        const content = section.acceptChanges();
        if (this.editingSection === sectionId) {
            this.editingSection = null;
        }

        this.emit('changes-accepted', { sectionId, content, section: section.getStatus() });
        return content;
    }

    /**
     * Cancel changes for a section
     * @param {string} sectionId - The section ID
     * @returns {string} The original content
     */
    cancelChanges(sectionId) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        const content = section.cancelChanges();
        if (this.editingSection === sectionId) {
            this.editingSection = null;
        }

        this.emit('changes-cancelled', { sectionId, content, section: section.getStatus() });
        return content;
    }

    /**
     * Reset section to original content
     * @param {string} sectionId - The section ID
     * @returns {string} The original content
     */
    resetToOriginal(sectionId) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        const content = section.resetToOriginal();
        this.emit('section-reset', { sectionId, content, section: section.getStatus() });
        return content;
    }

    /**
     * Stop editing a section (preserves changes as modified)
     * @param {string} sectionId - The section ID
     * @returns {EditState} The new state
     */
    stopEditing(sectionId) {
        const section = this.sections.get(sectionId);
        if (!section) {
            throw new Error(`Section ${sectionId} not found`);
        }

        const newState = section.stopEditing();
        if (this.editingSection === sectionId) {
            this.editingSection = null;
        }

        this.emit('edit-stopped', { sectionId, newState, section: section.getStatus() });
        return newState;
    }

    /**
     * Get the currently editing section
     * @returns {Section|null}
     */
    getCurrentlyEditing() {
        return this.editingSection ? this.sections.get(this.editingSection) : null;
    }

    /**
     * Get all sections
     * @returns {Array<Section>}
     */
    getAllSections() {
        return Array.from(this.sections.values());
    }

    /**
     * Get sections by state
     * @param {EditState} state - The state to filter by
     * @returns {Array<Section>}
     */
    getSectionsByState(state) {
        return this.getAllSections().filter(section => section.state === state);
    }

    /**
     * Get document status
     * @returns {Object}
     */
    getDocumentStatus() {
        const sections = this.getAllSections();
        const modified = sections.filter(s => s.isModified()).length;
        const editing = sections.filter(s => s.isEditing()).length;
        const saved = sections.filter(s => s.state === EditState.SAVED).length;

        return {
            totalSections: sections.length,
            modifiedSections: modified,
            editingSections: editing,
            savedSections: saved,
            hasUnsavedChanges: modified > 0 || editing > 0,
            currentlyEditing: this.editingSection
        };
    }

    /**
     * Reset all sections to original state
     */
    resetAllToOriginal() {
        for (const section of this.sections.values()) {
            section.resetToOriginal();
        }
        this.editingSection = null;
        this.emit('all-sections-reset', { status: this.getDocumentStatus() });
    }

    /**
     * Get the complete document markdown
     * @returns {string}
     */
    getDocumentMarkdown() {
        return this.getAllSections()
            .map(section => section.currentMarkdown)
            .join('\n\n');
    }
}

// Export for testing and usage
if (typeof module !== 'undefined' && module.exports) {
    module.exports = { Section, SectionManager, EditState, SectionType };
} else {
    window.MarkitectEditor = { Section, SectionManager, EditState, SectionType };
}